From c71cd32b340861735a3e4570a7d58483ec91cd1d Mon Sep 17 00:00:00 2001 From: Nikhil Rao Date: Wed, 25 Aug 2010 23:34:19 -0700 Subject: [PATCH] Add google appengine to repo --- google_appengine/BUGS | 3 + google_appengine/LICENSE | 132 + google_appengine/README | 123 + google_appengine/RELEASE_NOTES | 611 ++ google_appengine/VERSION | 3 + google_appengine/appcfg.py | 69 + google_appengine/bulkload_client.py | 69 + google_appengine/bulkloader.py | 69 + google_appengine/demos/guestbook/app.yaml | 8 + google_appengine/demos/guestbook/guestbook.py | 79 + google_appengine/dev_appserver.py | 69 + google_appengine/google/__init__.py | 16 + google_appengine/google/__init__.pyc | Bin 0 -> 144 bytes google_appengine/google/appengine/__init__.py | 16 + google_appengine/google/appengine/__init__.pyc | Bin 0 -> 154 bytes google_appengine/google/appengine/api/__init__.py | 16 + google_appengine/google/appengine/api/__init__.pyc | Bin 0 -> 158 bytes .../google/appengine/api/api_base_pb.py | 582 ++ .../google/appengine/api/api_base_pb.pyc | Bin 0 -> 26446 bytes .../google/appengine/api/apiproxy_rpc.py | 165 + .../google/appengine/api/apiproxy_rpc.pyc | Bin 0 -> 6085 bytes .../google/appengine/api/apiproxy_stub.py | 80 + .../google/appengine/api/apiproxy_stub.pyc | Bin 0 -> 2872 bytes .../google/appengine/api/apiproxy_stub_map.py | 513 ++ .../google/appengine/api/apiproxy_stub_map.pyc | Bin 0 -> 20354 bytes .../google/appengine/api/app_logging.py | 99 + google_appengine/google/appengine/api/appinfo.py | 529 ++ google_appengine/google/appengine/api/appinfo.pyc | Bin 0 -> 17042 bytes .../google/appengine/api/appinfo_errors.py | 46 + .../google/appengine/api/appinfo_errors.pyc | Bin 0 -> 2582 bytes .../google/appengine/api/blobstore/__init__.py | 20 + .../google/appengine/api/blobstore/__init__.pyc | Bin 0 -> 254 bytes .../google/appengine/api/blobstore/blobstore.py | 278 + .../google/appengine/api/blobstore/blobstore.pyc | Bin 0 -> 8986 bytes .../api/blobstore/blobstore_service_pb.py | 773 ++ .../api/blobstore/blobstore_service_pb.pyc | Bin 0 -> 38346 bytes .../appengine/api/blobstore/blobstore_stub.py | 289 + .../appengine/api/blobstore/blobstore_stub.pyc | Bin 0 -> 10890 bytes .../appengine/api/blobstore/file_blob_storage.py | 152 + .../appengine/api/blobstore/file_blob_storage.pyc | Bin 0 -> 4872 bytes .../google/appengine/api/capabilities/__init__.py | 172 + .../google/appengine/api/capabilities/__init__.pyc | Bin 0 -> 5968 bytes .../api/capabilities/capability_service_pb.py | 367 + .../api/capabilities/capability_service_pb.pyc | Bin 0 -> 18156 bytes .../appengine/api/capabilities/capability_stub.py | 53 + .../appengine/api/capabilities/capability_stub.pyc | Bin 0 -> 1770 bytes .../google/appengine/api/channel/__init__.py | 20 + .../google/appengine/api/channel/__init__.pyc | Bin 0 -> 248 bytes .../google/appengine/api/channel/channel.py | 143 + .../google/appengine/api/channel/channel.pyc | Bin 0 -> 4791 bytes .../appengine/api/channel/channel_service_pb.py | 382 + .../appengine/api/channel/channel_service_pb.pyc | Bin 0 -> 18005 bytes .../appengine/api/channel/channel_service_stub.py | 173 + .../appengine/api/channel/channel_service_stub.pyc | Bin 0 -> 6196 bytes google_appengine/google/appengine/api/croninfo.py | 132 + google_appengine/google/appengine/api/croninfo.pyc | Bin 0 -> 4863 bytes google_appengine/google/appengine/api/datastore.py | 2576 ++++++ .../google/appengine/api/datastore.pyc | Bin 0 -> 87371 bytes .../google/appengine/api/datastore_admin.py | 133 + .../google/appengine/api/datastore_admin.pyc | Bin 0 -> 4480 bytes .../google/appengine/api/datastore_entities.py | 343 + .../google/appengine/api/datastore_errors.py | 113 + .../google/appengine/api/datastore_errors.pyc | Bin 0 -> 6633 bytes .../google/appengine/api/datastore_file_stub.py | 1337 +++ .../google/appengine/api/datastore_file_stub.pyc | Bin 0 -> 46256 bytes .../google/appengine/api/datastore_types.py | 1722 ++++ .../google/appengine/api/datastore_types.pyc | Bin 0 -> 61731 bytes google_appengine/google/appengine/api/dosinfo.py | 112 + google_appengine/google/appengine/api/dosinfo.pyc | Bin 0 -> 3919 bytes .../google/appengine/api/images/__init__.py | 978 +++ .../google/appengine/api/images/__init__.pyc | Bin 0 -> 33935 bytes .../api/images/images_not_implemented_stub.py | 36 + .../appengine/api/images/images_service_pb.py | 2188 +++++ .../appengine/api/images/images_service_pb.pyc | Bin 0 -> 102845 bytes .../google/appengine/api/images/images_stub.py | 502 ++ .../google/appengine/api/images/images_stub.pyc | Bin 0 -> 16704 bytes .../google/appengine/api/labs/__init__.py | 16 + .../google/appengine/api/labs/__init__.pyc | Bin 0 -> 163 bytes .../appengine/api/labs/taskqueue/__init__.py | 20 + .../appengine/api/labs/taskqueue/__init__.pyc | Bin 0 -> 260 bytes .../appengine/api/labs/taskqueue/taskqueue.py | 805 ++ .../appengine/api/labs/taskqueue/taskqueue.pyc | Bin 0 -> 33400 bytes .../api/labs/taskqueue/taskqueue_service_pb.py | 4588 ++++++++++ .../api/labs/taskqueue/taskqueue_service_pb.pyc | Bin 0 -> 224786 bytes .../appengine/api/labs/taskqueue/taskqueue_stub.py | 956 +++ .../api/labs/taskqueue/taskqueue_stub.pyc | Bin 0 -> 37855 bytes .../google/appengine/api/lib_config.py | 342 + .../google/appengine/api/lib_config.pyc | Bin 0 -> 11633 bytes google_appengine/google/appengine/api/mail.py | 1180 +++ google_appengine/google/appengine/api/mail.pyc | Bin 0 -> 38392 bytes .../google/appengine/api/mail_errors.py | 55 + .../google/appengine/api/mail_errors.pyc | Bin 0 -> 3391 bytes .../google/appengine/api/mail_service_pb.py | 585 ++ .../google/appengine/api/mail_service_pb.pyc | Bin 0 -> 26633 bytes google_appengine/google/appengine/api/mail_stub.py | 233 + .../google/appengine/api/mail_stub.pyc | Bin 0 -> 7996 bytes .../google/appengine/api/memcache/__init__.py | 1045 +++ .../google/appengine/api/memcache/__init__.pyc | Bin 0 -> 39284 bytes .../appengine/api/memcache/memcache_service_pb.py | 2767 ++++++ .../appengine/api/memcache/memcache_service_pb.pyc | Bin 0 -> 133790 bytes .../google/appengine/api/memcache/memcache_stub.py | 330 + .../appengine/api/memcache/memcache_stub.pyc | Bin 0 -> 11923 bytes .../appengine/api/namespace_manager/__init__.py | 21 + .../appengine/api/namespace_manager/__init__.pyc | Bin 0 -> 274 bytes .../api/namespace_manager/namespace_manager.py | 115 + .../api/namespace_manager/namespace_manager.pyc | Bin 0 -> 4233 bytes .../google/appengine/api/oauth/__init__.py | 32 + .../google/appengine/api/oauth/oauth_api.py | 190 + google_appengine/google/appengine/api/queueinfo.py | 195 + .../google/appengine/api/queueinfo.pyc | Bin 0 -> 6562 bytes google_appengine/google/appengine/api/quota.py | 71 + google_appengine/google/appengine/api/urlfetch.py | 362 + google_appengine/google/appengine/api/urlfetch.pyc | Bin 0 -> 12492 bytes .../google/appengine/api/urlfetch_errors.py | 57 + .../google/appengine/api/urlfetch_errors.pyc | Bin 0 -> 2305 bytes .../google/appengine/api/urlfetch_service_pb.py | 943 ++ .../google/appengine/api/urlfetch_service_pb.pyc | Bin 0 -> 43800 bytes .../google/appengine/api/urlfetch_stub.py | 285 + .../google/appengine/api/urlfetch_stub.pyc | Bin 0 -> 8749 bytes .../google/appengine/api/user_service_pb.py | 1329 +++ .../google/appengine/api/user_service_pb.pyc | Bin 0 -> 61454 bytes .../google/appengine/api/user_service_stub.py | 133 + .../google/appengine/api/user_service_stub.pyc | Bin 0 -> 4913 bytes google_appengine/google/appengine/api/users.py | 281 + google_appengine/google/appengine/api/users.pyc | Bin 0 -> 10537 bytes .../google/appengine/api/validation.py | 945 ++ .../google/appengine/api/validation.pyc | Bin 0 -> 36125 bytes .../google/appengine/api/xmpp/__init__.py | 332 + .../google/appengine/api/xmpp/__init__.pyc | Bin 0 -> 11304 bytes .../google/appengine/api/xmpp/xmpp_service_pb.py | 1092 +++ .../google/appengine/api/xmpp/xmpp_service_pb.pyc | Bin 0 -> 50096 bytes .../google/appengine/api/xmpp/xmpp_service_stub.py | 154 + .../appengine/api/xmpp/xmpp_service_stub.pyc | Bin 0 -> 4908 bytes .../google/appengine/api/yaml_builder.py | 432 + .../google/appengine/api/yaml_builder.pyc | Bin 0 -> 15951 bytes .../google/appengine/api/yaml_errors.py | 96 + .../google/appengine/api/yaml_errors.pyc | Bin 0 -> 4969 bytes .../google/appengine/api/yaml_listener.py | 218 + .../google/appengine/api/yaml_listener.pyc | Bin 0 -> 9472 bytes .../google/appengine/api/yaml_object.py | 294 + .../google/appengine/api/yaml_object.pyc | Bin 0 -> 10882 bytes google_appengine/google/appengine/base/__init__.py | 16 + .../google/appengine/base/__init__.pyc | Bin 0 -> 159 bytes .../google/appengine/base/capabilities_pb.py | 451 + .../google/appengine/base/capabilities_pb.pyc | Bin 0 -> 20418 bytes .../google/appengine/cron/GrocLexer.py | 1697 ++++ .../google/appengine/cron/GrocLexer.pyc | Bin 0 -> 22957 bytes .../google/appengine/cron/GrocParser.py | 1137 +++ .../google/appengine/cron/GrocParser.pyc | Bin 0 -> 20254 bytes google_appengine/google/appengine/cron/__init__.py | 17 + .../google/appengine/cron/__init__.pyc | Bin 0 -> 224 bytes google_appengine/google/appengine/cron/groc.py | 74 + google_appengine/google/appengine/cron/groc.pyc | Bin 0 -> 2427 bytes .../google/appengine/cron/groctimespecification.py | 330 + .../appengine/cron/groctimespecification.pyc | Bin 0 -> 11567 bytes .../google/appengine/datastore/__init__.py | 16 + .../google/appengine/datastore/__init__.pyc | Bin 0 -> 164 bytes .../google/appengine/datastore/action_pb.py | 24 + .../google/appengine/datastore/action_pb.pyc | Bin 0 -> 619 bytes .../google/appengine/datastore/datastore_index.py | 531 ++ .../google/appengine/datastore/datastore_index.pyc | Bin 0 -> 16942 bytes .../google/appengine/datastore/datastore_pb.py | 5797 +++++++++++++ .../google/appengine/datastore/datastore_pb.pyc | Bin 0 -> 267147 bytes .../appengine/datastore/datastore_sqlite_stub.py | 1574 ++++ .../appengine/datastore/datastore_sqlite_stub.pyc | Bin 0 -> 59940 bytes .../google/appengine/datastore/datastore_v3_pb.py | 26 + .../google/appengine/datastore/datastore_v3_pb.pyc | Bin 0 -> 535 bytes .../google/appengine/datastore/entity_pb.py | 2779 ++++++ .../google/appengine/datastore/entity_pb.pyc | Bin 0 -> 126396 bytes .../appengine/datastore/sortable_pb_encoder.py | 282 + .../appengine/datastore/sortable_pb_encoder.pyc | Bin 0 -> 10713 bytes google_appengine/google/appengine/dist/__init__.py | 36 + .../google/appengine/dist/__init__.pyc | Bin 0 -> 489 bytes google_appengine/google/appengine/dist/_library.py | 288 + .../google/appengine/dist/_library.pyc | Bin 0 -> 9523 bytes google_appengine/google/appengine/dist/ftplib.py | 16 + google_appengine/google/appengine/dist/httplib.py | 388 + google_appengine/google/appengine/dist/neo_cgi.py | 16 + google_appengine/google/appengine/dist/py_imp.py | 142 + .../google/appengine/dist/py_zipimport.py | 291 + .../google/appengine/dist/py_zipimport.pyc | Bin 0 -> 11000 bytes google_appengine/google/appengine/dist/select.py | 16 + google_appengine/google/appengine/dist/socket.py | 45 + .../google/appengine/dist/subprocess.py | 16 + google_appengine/google/appengine/dist/tempfile.py | 65 + google_appengine/google/appengine/ext/__init__.py | 16 + google_appengine/google/appengine/ext/__init__.pyc | Bin 0 -> 158 bytes .../google/appengine/ext/admin/__init__.py | 1411 +++ .../google/appengine/ext/admin/templates/base.html | 94 + .../google/appengine/ext/admin/templates/cron.html | 85 + .../appengine/ext/admin/templates/css/ae.css | 170 + .../appengine/ext/admin/templates/css/base.css | 2 + .../appengine/ext/admin/templates/css/cron.css | 26 + .../ext/admin/templates/css/datastore.css | 71 + .../appengine/ext/admin/templates/css/form.css | 20 + .../ext/admin/templates/css/inboundmail.css | 19 + .../appengine/ext/admin/templates/css/memcache.css | 54 + .../appengine/ext/admin/templates/css/nav.css | 88 + .../appengine/ext/admin/templates/css/pager.css | 7 + .../appengine/ext/admin/templates/css/queues.css | 26 + .../appengine/ext/admin/templates/css/tasks.css | 26 + .../appengine/ext/admin/templates/css/xmpp.css | 19 + .../appengine/ext/admin/templates/datastore.html | 243 + .../ext/admin/templates/datastore_edit.html | 184 + .../ext/admin/templates/images/google.gif | Bin 0 -> 1470 bytes .../appengine/ext/admin/templates/inboundmail.html | 158 + .../ext/admin/templates/interactive-output.html | 36 + .../appengine/ext/admin/templates/interactive.html | 104 + .../ext/admin/templates/js/multipart_form_data.js | 125 + .../ext/admin/templates/js/rfc822_date.js | 70 + .../appengine/ext/admin/templates/js/webhook.js | 99 + .../appengine/ext/admin/templates/memcache.html | 119 + .../appengine/ext/admin/templates/pager.html | 9 + .../appengine/ext/admin/templates/queues.html | 75 + .../appengine/ext/admin/templates/tasks.html | 103 + .../google/appengine/ext/admin/templates/xmpp.html | 234 + .../google/appengine/ext/appstats/__init__.py | 16 + .../google/appengine/ext/appstats/datamodel_pb.py | 1326 +++ .../google/appengine/ext/appstats/formatting.py | 229 + .../google/appengine/ext/appstats/recording.py | 971 +++ .../ext/appstats/sample_appengine_config.py | 211 + .../ext/appstats/static/app_engine_logo_sm.gif | Bin 0 -> 2947 bytes .../appengine/ext/appstats/static/appstats_css.css | 2 + .../appengine/ext/appstats/static/appstats_js.js | 82 + .../google/appengine/ext/appstats/static/gantt.js | 282 + .../google/appengine/ext/appstats/static/minus.gif | Bin 0 -> 199 bytes .../google/appengine/ext/appstats/static/pix.gif | Bin 0 -> 43 bytes .../google/appengine/ext/appstats/static/plus.gif | Bin 0 -> 203 bytes .../appengine/ext/appstats/templates/base.html | 39 + .../appengine/ext/appstats/templates/details.html | 214 + .../appengine/ext/appstats/templates/file.html | 15 + .../appengine/ext/appstats/templates/main.html | 166 + .../appengine/ext/appstats/templates/test.html | 2 + .../google/appengine/ext/appstats/ui.py | 288 + .../google/appengine/ext/blobstore/__init__.py | 20 + .../google/appengine/ext/blobstore/blobstore.py | 703 ++ .../google/appengine/ext/bulkload/__init__.py | 37 + .../google/appengine/ext/bulkload/__init__.pyc | Bin 0 -> 879 bytes .../appengine/ext/bulkload/bulkload_deprecated.py | 359 + .../appengine/ext/bulkload/bulkload_deprecated.pyc | Bin 0 -> 12820 bytes .../appengine/ext/bulkload/bulkloader_config.py | 550 ++ .../appengine/ext/bulkload/bulkloader_config.pyc | Bin 0 -> 21320 bytes .../appengine/ext/bulkload/bulkloader_errors.py | 44 + .../appengine/ext/bulkload/bulkloader_errors.pyc | Bin 0 -> 2012 bytes .../appengine/ext/bulkload/bulkloader_parser.py | 316 + .../appengine/ext/bulkload/bulkloader_parser.pyc | Bin 0 -> 11944 bytes .../appengine/ext/bulkload/bulkloader_wizard.py | 154 + .../appengine/ext/bulkload/bulkloader_wizard.yaml | 87 + .../appengine/ext/bulkload/connector_interface.py | 99 + .../appengine/ext/bulkload/connector_interface.pyc | Bin 0 -> 4021 bytes .../google/appengine/ext/bulkload/constants.py | 24 + .../google/appengine/ext/bulkload/constants.pyc | Bin 0 -> 345 bytes .../google/appengine/ext/bulkload/csv_connector.py | 237 + .../appengine/ext/bulkload/csv_connector.pyc | Bin 0 -> 8675 bytes .../appengine/ext/bulkload/simpletext_connector.py | 112 + .../ext/bulkload/simpletext_connector.pyc | Bin 0 -> 4307 bytes .../appengine/ext/bulkload/simplexml_connector.py | 205 + .../appengine/ext/bulkload/simplexml_connector.pyc | Bin 0 -> 7373 bytes .../google/appengine/ext/bulkload/transform.py | 561 ++ .../google/appengine/ext/db/__init__.py | 3432 ++++++++ .../google/appengine/ext/db/__init__.pyc | Bin 0 -> 123459 bytes .../google/appengine/ext/db/djangoforms.py | 904 ++ .../google/appengine/ext/db/polymodel.py | 355 + .../google/appengine/ext/db/polymodel.pyc | Bin 0 -> 13705 bytes google_appengine/google/appengine/ext/db/stats.py | 145 + google_appengine/google/appengine/ext/db/stats.pyc | Bin 0 -> 6082 bytes .../google/appengine/ext/deferred/__init__.py | 26 + .../google/appengine/ext/deferred/deferred.py | 276 + .../google/appengine/ext/deferred/handler.py | 36 + .../google/appengine/ext/ereporter/__init__.py | 18 + .../google/appengine/ext/ereporter/ereporter.py | 261 + .../appengine/ext/ereporter/report_generator.py | 184 + .../appengine/ext/ereporter/templates/report.html | 15 + .../google/appengine/ext/gql/__init__.py | 1171 +++ .../google/appengine/ext/key_range/__init__.py | 617 ++ .../google/appengine/ext/key_range/__init__.pyc | Bin 0 -> 20212 bytes .../google/appengine/ext/preload/__init__.py | 213 + .../google/appengine/ext/remote_api/__init__.py | 16 + .../google/appengine/ext/remote_api/__init__.pyc | Bin 0 -> 169 bytes .../google/appengine/ext/remote_api/handler.py | 277 + .../appengine/ext/remote_api/remote_api_pb.py | 809 ++ .../appengine/ext/remote_api/remote_api_pb.pyc | Bin 0 -> 37514 bytes .../ext/remote_api/remote_api_services.py | 128 + .../ext/remote_api/remote_api_services.pyc | Bin 0 -> 4330 bytes .../appengine/ext/remote_api/remote_api_stub.py | 538 ++ .../appengine/ext/remote_api/remote_api_stub.pyc | Bin 0 -> 21712 bytes .../google/appengine/ext/remote_api/throttle.py | 643 ++ .../google/appengine/ext/remote_api/throttle.pyc | Bin 0 -> 24906 bytes .../google/appengine/ext/search/__init__.py | 420 + .../google/appengine/ext/search/__init__.pyc | Bin 0 -> 17410 bytes .../google/appengine/ext/webapp/__init__.py | 592 ++ .../google/appengine/ext/webapp/__init__.pyc | Bin 0 -> 22826 bytes .../appengine/ext/webapp/blobstore_handlers.py | 296 + .../google/appengine/ext/webapp/mail_handlers.py | 78 + .../google/appengine/ext/webapp/template.py | 219 + .../google/appengine/ext/webapp/util.py | 129 + .../google/appengine/ext/webapp/xmpp_handlers.py | 119 + .../google/appengine/ext/zipserve/__init__.py | 173 + .../google/appengine/runtime/__init__.py | 33 + .../google/appengine/runtime/__init__.pyc | Bin 0 -> 772 bytes .../google/appengine/runtime/apiproxy.py | 199 + .../google/appengine/runtime/apiproxy.pyc | Bin 0 -> 6741 bytes .../google/appengine/runtime/apiproxy_errors.py | 87 + .../google/appengine/runtime/apiproxy_errors.pyc | Bin 0 -> 5263 bytes .../google/appengine/tools/__init__.py | 16 + .../google/appengine/tools/__init__.pyc | Bin 0 -> 160 bytes .../google/appengine/tools/adaptive_thread_pool.py | 461 + .../appengine/tools/adaptive_thread_pool.pyc | Bin 0 -> 17418 bytes google_appengine/google/appengine/tools/appcfg.py | 2875 +++++++ google_appengine/google/appengine/tools/appcfg.pyc | Bin 0 -> 103418 bytes .../google/appengine/tools/appengine_rpc.py | 445 + .../google/appengine/tools/appengine_rpc.pyc | Bin 0 -> 17426 bytes .../google/appengine/tools/bulkload_client.py | 297 + .../google/appengine/tools/bulkloader.py | 4033 +++++++++ .../google/appengine/tools/bulkloader.pyc | Bin 0 -> 144762 bytes .../google/appengine/tools/dev-channel-js.js | 5633 ++++++++++++ .../google/appengine/tools/dev_appserver.py | 3892 +++++++++ .../google/appengine/tools/dev_appserver.pyc | Bin 0 -> 131109 bytes .../appengine/tools/dev_appserver_blobimage.py | 192 + .../appengine/tools/dev_appserver_blobimage.pyc | Bin 0 -> 6531 bytes .../appengine/tools/dev_appserver_blobstore.py | 345 + .../appengine/tools/dev_appserver_blobstore.pyc | Bin 0 -> 11290 bytes .../appengine/tools/dev_appserver_channel.py | 103 + .../appengine/tools/dev_appserver_channel.pyc | Bin 0 -> 3465 bytes .../google/appengine/tools/dev_appserver_index.py | 277 + .../google/appengine/tools/dev_appserver_index.pyc | Bin 0 -> 8605 bytes .../google/appengine/tools/dev_appserver_login.py | 297 + .../google/appengine/tools/dev_appserver_login.pyc | Bin 0 -> 8758 bytes .../google/appengine/tools/dev_appserver_main.py | 449 + .../google/appengine/tools/dev_appserver_oauth.py | 262 + .../google/appengine/tools/dev_appserver_oauth.pyc | Bin 0 -> 8275 bytes .../google/appengine/tools/dev_appserver_upload.py | 306 + .../appengine/tools/dev_appserver_upload.pyc | Bin 0 -> 10782 bytes .../google/appengine/tools/os_compat.py | 46 + .../google/appengine/tools/os_compat.pyc | Bin 0 -> 1184 bytes .../google/appengine/tools/remote_api_shell.py | 100 + google_appengine/google/appengine/tools/requeue.py | 219 + .../google/appengine/tools/requeue.pyc | Bin 0 -> 7964 bytes google_appengine/google/net/__init__.py | 16 + google_appengine/google/net/__init__.pyc | Bin 0 -> 148 bytes .../google/net/proto/ProtocolBuffer.py | 532 ++ .../google/net/proto/ProtocolBuffer.pyc | Bin 0 -> 23422 bytes google_appengine/google/net/proto/RawMessage.py | 83 + google_appengine/google/net/proto/RawMessage.pyc | Bin 0 -> 3781 bytes google_appengine/google/net/proto/__init__.py | 16 + google_appengine/google/net/proto/__init__.pyc | Bin 0 -> 154 bytes google_appengine/google/net/proto/message_set.py | 291 + google_appengine/google/net/proto/message_set.pyc | Bin 0 -> 10921 bytes google_appengine/google/pyglib/__init__.py | 16 + google_appengine/google/pyglib/__init__.pyc | Bin 0 -> 151 bytes google_appengine/google/pyglib/gexcept.py | 39 + google_appengine/google/pyglib/gexcept.pyc | Bin 0 -> 1772 bytes google_appengine/lib/antlr3/AUTHORS | 2 + google_appengine/lib/antlr3/LICENSE | 26 + google_appengine/lib/antlr3/MANIFEST.in | 2 + google_appengine/lib/antlr3/OWNERS | 7 + google_appengine/lib/antlr3/README | 90 + google_appengine/lib/antlr3/antlr3/__init__.py | 171 + google_appengine/lib/antlr3/antlr3/__init__.pyc | Bin 0 -> 4497 bytes google_appengine/lib/antlr3/antlr3/compat.py | 48 + google_appengine/lib/antlr3/antlr3/compat.pyc | Bin 0 -> 595 bytes google_appengine/lib/antlr3/antlr3/constants.py | 57 + google_appengine/lib/antlr3/antlr3/constants.pyc | Bin 0 -> 423 bytes google_appengine/lib/antlr3/antlr3/dfa.py | 213 + google_appengine/lib/antlr3/antlr3/dfa.pyc | Bin 0 -> 4620 bytes google_appengine/lib/antlr3/antlr3/dottreegen.py | 210 + google_appengine/lib/antlr3/antlr3/exceptions.py | 364 + google_appengine/lib/antlr3/antlr3/exceptions.pyc | Bin 0 -> 12864 bytes google_appengine/lib/antlr3/antlr3/extras.py | 47 + google_appengine/lib/antlr3/antlr3/main.py | 289 + google_appengine/lib/antlr3/antlr3/recognizers.py | 1511 ++++ google_appengine/lib/antlr3/antlr3/recognizers.pyc | Bin 0 -> 48358 bytes google_appengine/lib/antlr3/antlr3/streams.py | 1452 ++++ google_appengine/lib/antlr3/antlr3/streams.pyc | Bin 0 -> 44854 bytes google_appengine/lib/antlr3/antlr3/tokens.py | 416 + google_appengine/lib/antlr3/antlr3/tokens.pyc | Bin 0 -> 13956 bytes google_appengine/lib/antlr3/antlr3/tree.py | 2448 ++++++ google_appengine/lib/antlr3/antlr3/treewizard.py | 612 ++ .../antlr3/antlr_python_runtime.egg-info/PKG-INFO | 13 + .../antlr_python_runtime.egg-info/SOURCES.txt | 23 + .../dependency_links.txt | 1 + .../antlr_python_runtime.egg-info/top_level.txt | 1 + google_appengine/lib/antlr3/setup.py | 289 + google_appengine/lib/cacerts/cacerts.txt | 601 ++ google_appengine/lib/django/AUTHORS | 216 + google_appengine/lib/django/INSTALL | 22 + google_appengine/lib/django/LICENSE | 27 + google_appengine/lib/django/PKG-INFO | 11 + google_appengine/lib/django/README | 37 + google_appengine/lib/django/django/__init__.py | 1 + google_appengine/lib/django/django/bin/__init__.py | 0 .../lib/django/django/bin/compile-messages.py | 49 + .../lib/django/django/bin/daily_cleanup.py | 20 + .../lib/django/django/bin/django-admin.py | 5 + .../lib/django/django/bin/make-messages.py | 145 + .../lib/django/django/bin/profiling/__init__.py | 0 .../django/bin/profiling/gather_profile_stats.py | 34 + .../lib/django/django/bin/unique-messages.py | 28 + .../lib/django/django/conf/__init__.py | 149 + .../django/django/conf/app_template/__init__.py | 0 .../lib/django/django/conf/app_template/models.py | 3 + .../lib/django/django/conf/app_template/views.py | 1 + .../lib/django/django/conf/global_settings.py | 330 + .../django/conf/locale/ar/LC_MESSAGES/django.mo | Bin 0 -> 41884 bytes .../django/conf/locale/ar/LC_MESSAGES/django.po | 1989 +++++ .../django/conf/locale/ar/LC_MESSAGES/djangojs.mo | Bin 0 -> 1774 bytes .../django/conf/locale/ar/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/bn/LC_MESSAGES/django.mo | Bin 0 -> 25779 bytes .../django/conf/locale/bn/LC_MESSAGES/django.po | 1993 +++++ .../django/conf/locale/ca/LC_MESSAGES/django.mo | Bin 0 -> 31909 bytes .../django/conf/locale/ca/LC_MESSAGES/django.po | 2383 ++++++ .../django/conf/locale/ca/LC_MESSAGES/djangojs.mo | Bin 0 -> 1520 bytes .../django/conf/locale/ca/LC_MESSAGES/djangojs.po | 121 + .../django/conf/locale/cs/LC_MESSAGES/django.mo | Bin 0 -> 37754 bytes .../django/conf/locale/cs/LC_MESSAGES/django.po | 2110 +++++ .../django/conf/locale/cs/LC_MESSAGES/djangojs.mo | Bin 0 -> 1458 bytes .../django/conf/locale/cs/LC_MESSAGES/djangojs.po | 112 + .../django/conf/locale/cy/LC_MESSAGES/django.mo | Bin 0 -> 22852 bytes .../django/conf/locale/cy/LC_MESSAGES/django.po | 1990 +++++ .../django/conf/locale/cy/LC_MESSAGES/djangojs.mo | Bin 0 -> 1009 bytes .../django/conf/locale/cy/LC_MESSAGES/djangojs.po | 112 + .../django/conf/locale/da/LC_MESSAGES/django.mo | Bin 0 -> 32010 bytes .../django/conf/locale/da/LC_MESSAGES/django.po | 1927 +++++ .../django/conf/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 40423 bytes .../django/conf/locale/de/LC_MESSAGES/django.po | 2225 +++++ .../django/conf/locale/de/LC_MESSAGES/djangojs.mo | Bin 0 -> 1569 bytes .../django/conf/locale/de/LC_MESSAGES/djangojs.po | 119 + .../django/conf/locale/el/LC_MESSAGES/django.mo | Bin 0 -> 15668 bytes .../django/conf/locale/el/LC_MESSAGES/django.po | 1921 +++++ .../django/conf/locale/el/LC_MESSAGES/djangojs.mo | Bin 0 -> 1810 bytes .../django/conf/locale/el/LC_MESSAGES/djangojs.po | 109 + .../django/conf/locale/en/LC_MESSAGES/django.mo | Bin 0 -> 627 bytes .../django/conf/locale/en/LC_MESSAGES/django.po | 2097 +++++ .../django/conf/locale/en/LC_MESSAGES/djangojs.mo | Bin 0 -> 367 bytes .../django/conf/locale/en/LC_MESSAGES/djangojs.po | 108 + .../django/conf/locale/es/LC_MESSAGES/django.mo | Bin 0 -> 40248 bytes .../django/conf/locale/es/LC_MESSAGES/django.po | 2371 +++++ .../django/conf/locale/es/LC_MESSAGES/djangojs.mo | Bin 0 -> 1427 bytes .../django/conf/locale/es/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/es_AR/LC_MESSAGES/django.mo | Bin 0 -> 40281 bytes .../django/conf/locale/es_AR/LC_MESSAGES/django.po | 2425 ++++++ .../conf/locale/es_AR/LC_MESSAGES/djangojs.mo | Bin 0 -> 1576 bytes .../conf/locale/es_AR/LC_MESSAGES/djangojs.po | 118 + .../django/conf/locale/fi/LC_MESSAGES/django.mo | Bin 0 -> 34867 bytes .../django/conf/locale/fi/LC_MESSAGES/django.po | 2036 +++++ .../django/conf/locale/fi/LC_MESSAGES/djangojs.mo | Bin 0 -> 1529 bytes .../django/conf/locale/fi/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/fr/LC_MESSAGES/django.mo | Bin 0 -> 39707 bytes .../django/conf/locale/fr/LC_MESSAGES/django.po | 2513 ++++++ .../django/conf/locale/fr/LC_MESSAGES/djangojs.mo | Bin 0 -> 1533 bytes .../django/conf/locale/fr/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/gl/LC_MESSAGES/django.mo | Bin 0 -> 32192 bytes .../django/conf/locale/gl/LC_MESSAGES/django.po | 1988 +++++ .../django/conf/locale/gl/LC_MESSAGES/djangojs.mo | Bin 0 -> 1519 bytes .../django/conf/locale/gl/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/he/LC_MESSAGES/django.mo | Bin 0 -> 36826 bytes .../django/conf/locale/he/LC_MESSAGES/django.po | 1999 +++++ .../django/conf/locale/he/LC_MESSAGES/djangojs.mo | Bin 0 -> 1626 bytes .../django/conf/locale/he/LC_MESSAGES/djangojs.po | 111 + .../django/conf/locale/hu/LC_MESSAGES/django.mo | Bin 0 -> 30545 bytes .../django/conf/locale/hu/LC_MESSAGES/django.po | 2022 +++++ .../django/conf/locale/hu/LC_MESSAGES/djangojs.mo | Bin 0 -> 1556 bytes .../django/conf/locale/hu/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/is/LC_MESSAGES/django.mo | Bin 0 -> 29355 bytes .../django/conf/locale/is/LC_MESSAGES/django.po | 2042 +++++ .../django/conf/locale/is/LC_MESSAGES/djangojs.mo | Bin 0 -> 1609 bytes .../django/conf/locale/is/LC_MESSAGES/djangojs.po | 109 + .../django/conf/locale/it/LC_MESSAGES/django.mo | Bin 0 -> 40300 bytes .../django/conf/locale/it/LC_MESSAGES/django.po | 2297 +++++ .../django/conf/locale/it/LC_MESSAGES/djangojs.mo | Bin 0 -> 1643 bytes .../django/conf/locale/it/LC_MESSAGES/djangojs.po | 123 + .../django/conf/locale/ja/LC_MESSAGES/django.mo | Bin 0 -> 43571 bytes .../django/conf/locale/ja/LC_MESSAGES/django.po | 2327 +++++ .../django/conf/locale/ja/LC_MESSAGES/djangojs.mo | Bin 0 -> 1604 bytes .../django/conf/locale/ja/LC_MESSAGES/djangojs.po | 118 + .../django/conf/locale/kn/LC_MESSAGES/django.mo | Bin 0 -> 57878 bytes .../django/conf/locale/kn/LC_MESSAGES/django.po | 2533 ++++++ .../django/conf/locale/kn/LC_MESSAGES/djangojs.mo | Bin 0 -> 2205 bytes .../django/conf/locale/kn/LC_MESSAGES/djangojs.po | 116 + .../django/conf/locale/lv/LC_MESSAGES/django.mo | Bin 0 -> 26751 bytes .../django/conf/locale/lv/LC_MESSAGES/django.po | 2326 +++++ .../django/conf/locale/lv/LC_MESSAGES/djangojs.mo | Bin 0 -> 367 bytes .../django/conf/locale/lv/LC_MESSAGES/djangojs.po | 118 + .../django/conf/locale/mk/LC_MESSAGES/django.mo | Bin 0 -> 50847 bytes .../django/conf/locale/mk/LC_MESSAGES/django.po | 2320 +++++ .../django/conf/locale/mk/LC_MESSAGES/djangojs.mo | Bin 0 -> 1921 bytes .../django/conf/locale/mk/LC_MESSAGES/djangojs.po | 119 + .../django/conf/locale/nl/LC_MESSAGES/django.mo | Bin 0 -> 38004 bytes .../django/conf/locale/nl/LC_MESSAGES/django.po | 2277 +++++ .../django/conf/locale/nl/LC_MESSAGES/djangojs.mo | Bin 0 -> 1507 bytes .../django/conf/locale/nl/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/no/LC_MESSAGES/django.mo | Bin 0 -> 27469 bytes .../django/conf/locale/no/LC_MESSAGES/django.po | 2002 +++++ .../django/conf/locale/no/LC_MESSAGES/djangojs.mo | Bin 0 -> 1492 bytes .../django/conf/locale/no/LC_MESSAGES/djangojs.po | 118 + .../django/conf/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 28373 bytes .../django/conf/locale/pl/LC_MESSAGES/django.po | 1961 +++++ .../django/conf/locale/pl/LC_MESSAGES/djangojs.mo | Bin 0 -> 1564 bytes .../django/conf/locale/pl/LC_MESSAGES/djangojs.po | 112 + .../django/conf/locale/pt/LC_MESSAGES/django.mo | Bin 0 -> 37681 bytes .../django/conf/locale/pt/LC_MESSAGES/django.po | 2125 +++++ .../django/conf/locale/pt/LC_MESSAGES/djangojs.mo | Bin 0 -> 1514 bytes .../django/conf/locale/pt/LC_MESSAGES/djangojs.po | 108 + .../django/conf/locale/pt_BR/LC_MESSAGES/django.mo | Bin 0 -> 28462 bytes .../django/conf/locale/pt_BR/LC_MESSAGES/django.po | 2051 +++++ .../conf/locale/pt_BR/LC_MESSAGES/djangojs.mo | Bin 0 -> 1537 bytes .../conf/locale/pt_BR/LC_MESSAGES/djangojs.po | 109 + .../django/conf/locale/ro/LC_MESSAGES/django.mo | Bin 0 -> 16327 bytes .../django/conf/locale/ro/LC_MESSAGES/django.po | 2005 +++++ .../django/conf/locale/ru/LC_MESSAGES/django.mo | Bin 0 -> 42820 bytes .../django/conf/locale/ru/LC_MESSAGES/django.po | 1906 ++++ .../django/conf/locale/ru/LC_MESSAGES/djangojs.mo | Bin 0 -> 1746 bytes .../django/conf/locale/ru/LC_MESSAGES/djangojs.po | 111 + .../django/conf/locale/sk/LC_MESSAGES/django.mo | Bin 0 -> 32375 bytes .../django/conf/locale/sk/LC_MESSAGES/django.po | 2002 +++++ .../django/conf/locale/sk/LC_MESSAGES/djangojs.mo | Bin 0 -> 1492 bytes .../django/conf/locale/sk/LC_MESSAGES/djangojs.po | 111 + .../django/conf/locale/sl/LC_MESSAGES/django.mo | Bin 0 -> 32469 bytes .../django/conf/locale/sl/LC_MESSAGES/django.po | 1929 +++++ .../django/conf/locale/sr/LC_MESSAGES/django.mo | Bin 0 -> 32246 bytes .../django/conf/locale/sr/LC_MESSAGES/django.po | 1916 +++++ .../django/conf/locale/sr/LC_MESSAGES/djangojs.mo | Bin 0 -> 1669 bytes .../django/conf/locale/sr/LC_MESSAGES/djangojs.po | 109 + .../django/conf/locale/sv/LC_MESSAGES/django.mo | Bin 0 -> 39640 bytes .../django/conf/locale/sv/LC_MESSAGES/django.po | 2344 +++++ .../django/conf/locale/sv/LC_MESSAGES/djangojs.mo | Bin 0 -> 1680 bytes .../django/conf/locale/sv/LC_MESSAGES/djangojs.po | 125 + .../django/conf/locale/ta/LC_MESSAGES/django.mo | Bin 0 -> 60022 bytes .../django/conf/locale/ta/LC_MESSAGES/django.po | 2136 +++++ .../django/conf/locale/ta/LC_MESSAGES/djangojs.mo | Bin 0 -> 2336 bytes .../django/conf/locale/ta/LC_MESSAGES/djangojs.po | 112 + .../django/conf/locale/te/LC_MESSAGES/django.mo | Bin 0 -> 35017 bytes .../django/conf/locale/te/LC_MESSAGES/django.po | 2106 +++++ .../django/conf/locale/te/LC_MESSAGES/djangojs.mo | Bin 0 -> 2206 bytes .../django/conf/locale/te/LC_MESSAGES/djangojs.po | 110 + .../django/conf/locale/tr/LC_MESSAGES/django.mo | Bin 0 -> 38712 bytes .../django/conf/locale/tr/LC_MESSAGES/django.po | 2470 ++++++ .../django/conf/locale/tr/LC_MESSAGES/djangojs.mo | Bin 0 -> 1508 bytes .../django/conf/locale/tr/LC_MESSAGES/djangojs.po | 109 + .../django/conf/locale/uk/LC_MESSAGES/django.mo | Bin 0 -> 24836 bytes .../django/conf/locale/uk/LC_MESSAGES/django.po | 1970 +++++ .../django/conf/locale/zh_CN/LC_MESSAGES/django.mo | Bin 0 -> 30798 bytes .../django/conf/locale/zh_CN/LC_MESSAGES/django.po | 1879 ++++ .../conf/locale/zh_CN/LC_MESSAGES/djangojs.mo | Bin 0 -> 1505 bytes .../conf/locale/zh_CN/LC_MESSAGES/djangojs.po | 105 + .../django/conf/locale/zh_TW/LC_MESSAGES/django.mo | Bin 0 -> 28397 bytes .../django/conf/locale/zh_TW/LC_MESSAGES/django.po | 1974 +++++ .../django/conf/project_template/__init__.py | 0 .../django/django/conf/project_template/manage.py | 11 + .../django/conf/project_template/settings.py | 80 + .../django/django/conf/project_template/urls.py | 9 + .../lib/django/django/conf/urls/__init__.py | 0 .../lib/django/django/conf/urls/defaults.py | 19 + .../lib/django/django/conf/urls/i18n.py | 5 + .../lib/django/django/conf/urls/shortcut.py | 5 + .../lib/django/django/contrib/__init__.py | 0 .../lib/django/django/contrib/admin/__init__.py | 0 .../lib/django/django/contrib/admin/filterspecs.py | 175 + .../django/django/contrib/admin/media/css/base.css | 14 + .../django/contrib/admin/media/css/changelists.css | 50 + .../django/contrib/admin/media/css/dashboard.css | 10 + .../django/contrib/admin/media/css/forms.css | 60 + .../django/contrib/admin/media/css/global.css | 141 + .../django/contrib/admin/media/css/layout.css | 29 + .../django/contrib/admin/media/css/login.css | 13 + .../django/contrib/admin/media/css/patch-iewin.css | 8 + .../django/django/contrib/admin/media/css/rtl.css | 46 + .../django/contrib/admin/media/css/widgets.css | 101 + .../contrib/admin/media/img/admin/arrow-down.gif | Bin 0 -> 80 bytes .../contrib/admin/media/img/admin/arrow-up.gif | Bin 0 -> 838 bytes .../admin/media/img/admin/changelist-bg.gif | Bin 0 -> 58 bytes .../contrib/admin/media/img/admin/chooser-bg.gif | Bin 0 -> 199 bytes .../admin/media/img/admin/chooser_stacked-bg.gif | Bin 0 -> 212 bytes .../admin/media/img/admin/default-bg-reverse.gif | Bin 0 -> 843 bytes .../contrib/admin/media/img/admin/default-bg.gif | Bin 0 -> 844 bytes .../admin/media/img/admin/deleted-overlay.gif | Bin 0 -> 45 bytes .../contrib/admin/media/img/admin/icon-no.gif | Bin 0 -> 176 bytes .../contrib/admin/media/img/admin/icon-unknown.gif | Bin 0 -> 130 bytes .../contrib/admin/media/img/admin/icon-yes.gif | Bin 0 -> 299 bytes .../contrib/admin/media/img/admin/icon_addlink.gif | Bin 0 -> 119 bytes .../contrib/admin/media/img/admin/icon_alert.gif | Bin 0 -> 145 bytes .../admin/media/img/admin/icon_calendar.gif | Bin 0 -> 192 bytes .../admin/media/img/admin/icon_changelink.gif | Bin 0 -> 119 bytes .../contrib/admin/media/img/admin/icon_clock.gif | Bin 0 -> 390 bytes .../admin/media/img/admin/icon_deletelink.gif | Bin 0 -> 181 bytes .../contrib/admin/media/img/admin/icon_error.gif | Bin 0 -> 319 bytes .../admin/media/img/admin/icon_searchbox.png | Bin 0 -> 667 bytes .../contrib/admin/media/img/admin/icon_success.gif | Bin 0 -> 341 bytes .../admin/media/img/admin/inline-delete-8bit.png | Bin 0 -> 477 bytes .../admin/media/img/admin/inline-delete.png | Bin 0 -> 781 bytes .../admin/media/img/admin/inline-restore-8bit.png | Bin 0 -> 447 bytes .../admin/media/img/admin/inline-restore.png | Bin 0 -> 623 bytes .../admin/media/img/admin/inline-splitter-bg.gif | Bin 0 -> 102 bytes .../admin/media/img/admin/nav-bg-grabber.gif | Bin 0 -> 116 bytes .../admin/media/img/admin/nav-bg-reverse.gif | Bin 0 -> 186 bytes .../contrib/admin/media/img/admin/nav-bg.gif | Bin 0 -> 273 bytes .../contrib/admin/media/img/admin/selector-add.gif | Bin 0 -> 606 bytes .../admin/media/img/admin/selector-addall.gif | Bin 0 -> 358 bytes .../admin/media/img/admin/selector-remove.gif | Bin 0 -> 398 bytes .../admin/media/img/admin/selector-removeall.gif | Bin 0 -> 355 bytes .../admin/media/img/admin/selector-search.gif | Bin 0 -> 552 bytes .../admin/media/img/admin/selector_stacked-add.gif | Bin 0 -> 612 bytes .../media/img/admin/selector_stacked-remove.gif | Bin 0 -> 401 bytes .../contrib/admin/media/img/admin/tool-left.gif | Bin 0 -> 197 bytes .../admin/media/img/admin/tool-left_over.gif | Bin 0 -> 203 bytes .../contrib/admin/media/img/admin/tool-right.gif | Bin 0 -> 198 bytes .../admin/media/img/admin/tool-right_over.gif | Bin 0 -> 200 bytes .../contrib/admin/media/img/admin/tooltag-add.gif | Bin 0 -> 932 bytes .../admin/media/img/admin/tooltag-add_over.gif | Bin 0 -> 336 bytes .../admin/media/img/admin/tooltag-arrowright.gif | Bin 0 -> 351 bytes .../media/img/admin/tooltag-arrowright_over.gif | Bin 0 -> 354 bytes .../django/contrib/admin/media/js/SelectBox.js | 109 + .../django/contrib/admin/media/js/SelectFilter.js | 81 + .../django/contrib/admin/media/js/SelectFilter2.js | 113 + .../admin/media/js/admin/CollapsedFieldsets.js | 85 + .../admin/media/js/admin/DateTimeShortcuts.js | 241 + .../admin/media/js/admin/RelatedObjectLookups.js | 57 + .../contrib/admin/media/js/admin/ordering.js | 137 + .../django/contrib/admin/media/js/calendar.js | 143 + .../django/django/contrib/admin/media/js/core.js | 164 + .../django/contrib/admin/media/js/dateparse.js | 233 + .../admin/media/js/getElementsBySelector.js | 167 + .../django/contrib/admin/media/js/timeparse.js | 94 + .../django/django/contrib/admin/media/js/urlify.js | 15 + .../lib/django/django/contrib/admin/models.py | 51 + .../django/contrib/admin/templates/admin/404.html | 12 + .../django/contrib/admin/templates/admin/500.html | 12 + .../admin/templates/admin/auth/user/add_form.html | 28 + .../templates/admin/auth/user/change_password.html | 52 + .../django/contrib/admin/templates/admin/base.html | 55 + .../contrib/admin/templates/admin/base_site.html | 10 + .../contrib/admin/templates/admin/change_form.html | 70 + .../contrib/admin/templates/admin/change_list.html | 23 + .../admin/templates/admin/change_list_results.html | 17 + .../admin/templates/admin/date_hierarchy.html | 10 + .../admin/templates/admin/delete_confirmation.html | 30 + .../admin/templates/admin/edit_inline_stacked.html | 16 + .../admin/templates/admin/edit_inline_tabular.html | 44 + .../contrib/admin/templates/admin/field_line.html | 10 + .../contrib/admin/templates/admin/filter.html | 8 + .../contrib/admin/templates/admin/filters.html | 7 + .../contrib/admin/templates/admin/index.html | 67 + .../admin/templates/admin/invalid_setup.html | 10 + .../contrib/admin/templates/admin/login.html | 32 + .../admin/templates/admin/object_history.html | 43 + .../contrib/admin/templates/admin/pagination.html | 11 + .../contrib/admin/templates/admin/search_form.html | 18 + .../contrib/admin/templates/admin/submit_line.html | 8 + .../admin/templates/admin/template_validator.html | 31 + .../admin/templates/admin_doc/bookmarklets.html | 32 + .../contrib/admin/templates/admin_doc/index.html | 28 + .../templates/admin_doc/missing_docutils.html | 17 + .../admin/templates/admin_doc/model_detail.html | 47 + .../admin/templates/admin_doc/model_index.html | 45 + .../admin/templates/admin_doc/template_detail.html | 22 + .../templates/admin_doc/template_filter_index.html | 48 + .../templates/admin_doc/template_tag_index.html | 48 + .../admin/templates/admin_doc/view_detail.html | 26 + .../admin/templates/admin_doc/view_index.html | 43 + .../admin/templates/registration/logged_out.html | 12 + .../registration/password_change_done.html | 14 + .../registration/password_change_form.html | 26 + .../registration/password_reset_done.html | 14 + .../registration/password_reset_email.html | 15 + .../registration/password_reset_form.html | 19 + .../contrib/admin/templates/widget/date_time.html | 5 + .../contrib/admin/templates/widget/default.html | 1 + .../contrib/admin/templates/widget/file.html | 4 + .../contrib/admin/templates/widget/foreign.html | 20 + .../admin/templates/widget/many_to_many.html | 1 + .../contrib/admin/templates/widget/one_to_one.html | 2 + .../django/contrib/admin/templatetags/__init__.py | 0 .../contrib/admin/templatetags/admin_list.py | 279 + .../contrib/admin/templatetags/admin_modify.py | 247 + .../contrib/admin/templatetags/adminapplist.py | 79 + .../contrib/admin/templatetags/adminmedia.py | 14 + .../django/contrib/admin/templatetags/log.py | 53 + .../lib/django/django/contrib/admin/urls.py | 43 + .../lib/django/django/contrib/admin/utils.py | 102 + .../django/django/contrib/admin/views/__init__.py | 0 .../lib/django/django/contrib/admin/views/auth.py | 77 + .../django/contrib/admin/views/decorators.py | 73 + .../lib/django/django/contrib/admin/views/doc.py | 365 + .../lib/django/django/contrib/admin/views/main.py | 779 ++ .../django/django/contrib/admin/views/template.py | 72 + .../lib/django/django/contrib/auth/__init__.py | 77 + .../lib/django/django/contrib/auth/backends.py | 21 + .../django/django/contrib/auth/create_superuser.py | 91 + .../lib/django/django/contrib/auth/decorators.py | 36 + .../lib/django/django/contrib/auth/forms.py | 144 + .../django/contrib/auth/handlers/__init__.py | 0 .../django/contrib/auth/handlers/modpython.py | 52 + .../lib/django/django/contrib/auth/management.py | 49 + .../lib/django/django/contrib/auth/middleware.py | 12 + .../lib/django/django/contrib/auth/models.py | 315 + .../lib/django/django/contrib/auth/views.py | 85 + .../lib/django/django/contrib/comments/__init__.py | 0 .../lib/django/django/contrib/comments/feeds.py | 41 + .../lib/django/django/contrib/comments/models.py | 285 + .../contrib/comments/templates/comments/form.html | 38 + .../comments/templates/comments/freeform.html | 13 + .../contrib/comments/templatetags/__init__.py | 0 .../contrib/comments/templatetags/comments.py | 322 + .../django/contrib/comments/urls/__init__.py | 0 .../django/contrib/comments/urls/comments.py | 12 + .../django/contrib/comments/views/__init__.py | 0 .../django/contrib/comments/views/comments.py | 340 + .../django/django/contrib/comments/views/karma.py | 29 + .../django/contrib/comments/views/userflags.py | 54 + .../django/django/contrib/contenttypes/__init__.py | 0 .../django/contrib/contenttypes/management.py | 33 + .../django/django/contrib/contenttypes/models.py | 60 + .../lib/django/django/contrib/csrf/__init__.py | 0 .../lib/django/django/contrib/csrf/middleware.py | 92 + .../django/django/contrib/flatpages/__init__.py | 0 .../django/django/contrib/flatpages/middleware.py | 18 + .../lib/django/django/contrib/flatpages/models.py | 33 + .../lib/django/django/contrib/flatpages/urls.py | 5 + .../lib/django/django/contrib/flatpages/views.py | 38 + .../django/django/contrib/formtools/__init__.py | 0 .../lib/django/django/contrib/formtools/preview.py | 165 + .../lib/django/django/contrib/humanize/__init__.py | 0 .../contrib/humanize/templatetags/__init__.py | 0 .../contrib/humanize/templatetags/humanize.py | 69 + .../django/django/contrib/localflavor/__init__.py | 0 .../django/contrib/localflavor/uk/__init__.py | 0 .../django/django/contrib/localflavor/uk/forms.py | 19 + .../django/contrib/localflavor/usa/__init__.py | 0 .../django/django/contrib/localflavor/usa/forms.py | 59 + .../django/contrib/localflavor/usa/us_states.py | 239 + .../lib/django/django/contrib/markup/__init__.py | 0 .../django/contrib/markup/templatetags/__init__.py | 0 .../django/contrib/markup/templatetags/markup.py | 56 + .../django/django/contrib/redirects/__init__.py | 0 .../django/django/contrib/redirects/middleware.py | 27 + .../lib/django/django/contrib/redirects/models.py | 24 + .../lib/django/django/contrib/sessions/__init__.py | 0 .../django/django/contrib/sessions/middleware.py | 103 + .../lib/django/django/contrib/sessions/models.py | 88 + .../lib/django/django/contrib/sitemaps/__init__.py | 90 + .../django/contrib/sitemaps/templates/sitemap.xml | 13 + .../contrib/sitemaps/templates/sitemap_index.xml | 4 + .../lib/django/django/contrib/sitemaps/views.py | 30 + .../lib/django/django/contrib/sites/__init__.py | 0 .../lib/django/django/contrib/sites/management.py | 17 + .../lib/django/django/contrib/sites/managers.py | 20 + .../lib/django/django/contrib/sites/models.py | 23 + .../django/django/contrib/syndication/__init__.py | 0 .../lib/django/django/contrib/syndication/feeds.py | 122 + .../lib/django/django/contrib/syndication/views.py | 25 + .../lib/django/django/core/__init__.py | 0 .../lib/django/django/core/cache/__init__.py | 54 + .../django/django/core/cache/backends/__init__.py | 0 .../lib/django/django/core/cache/backends/base.py | 56 + .../lib/django/django/core/cache/backends/db.py | 82 + .../lib/django/django/core/cache/backends/dummy.py | 22 + .../django/django/core/cache/backends/filebased.py | 80 + .../django/django/core/cache/backends/locmem.py | 47 + .../django/django/core/cache/backends/memcached.py | 29 + .../django/django/core/cache/backends/simple.py | 64 + .../lib/django/django/core/context_processors.py | 69 + .../lib/django/django/core/exceptions.py | 25 + google_appengine/lib/django/django/core/handler.py | 11 + .../lib/django/django/core/handlers/__init__.py | 0 .../lib/django/django/core/handlers/base.py | 131 + .../lib/django/django/core/handlers/modpython.py | 177 + .../django/core/handlers/profiler-hotshot.py | 22 + .../lib/django/django/core/handlers/wsgi.py | 207 + google_appengine/lib/django/django/core/mail.py | 108 + .../lib/django/django/core/management.py | 1670 ++++ .../lib/django/django/core/paginator.py | 88 + .../lib/django/django/core/serializers/__init__.py | 90 + .../lib/django/django/core/serializers/base.py | 165 + .../lib/django/django/core/serializers/json.py | 51 + .../lib/django/django/core/serializers/python.py | 101 + .../lib/django/django/core/serializers/pyyaml.py | 36 + .../django/core/serializers/xml_serializer.py | 229 + .../lib/django/django/core/servers/__init__.py | 0 .../lib/django/django/core/servers/basehttp.py | 664 ++ .../lib/django/django/core/servers/fastcgi.py | 158 + google_appengine/lib/django/django/core/signals.py | 3 + .../lib/django/django/core/template_loader.py | 7 + .../lib/django/django/core/urlresolvers.py | 241 + .../lib/django/django/core/validators.py | 573 ++ .../lib/django/django/core/xheaders.py | 22 + google_appengine/lib/django/django/db/__init__.py | 48 + .../lib/django/django/db/backends/__init__.py | 0 .../django/db/backends/ado_mssql/__init__.py | 0 .../django/django/db/backends/ado_mssql/base.py | 167 + .../django/django/db/backends/ado_mssql/client.py | 2 + .../django/db/backends/ado_mssql/creation.py | 25 + .../django/db/backends/ado_mssql/introspection.py | 13 + .../django/django/db/backends/dummy/__init__.py | 0 .../lib/django/django/db/backends/dummy/base.py | 44 + .../lib/django/django/db/backends/dummy/client.py | 3 + .../django/django/db/backends/dummy/creation.py | 1 + .../django/db/backends/dummy/introspection.py | 8 + .../django/django/db/backends/mysql/__init__.py | 0 .../lib/django/django/db/backends/mysql/base.py | 231 + .../lib/django/django/db/backends/mysql/client.py | 27 + .../django/django/db/backends/mysql/creation.py | 29 + .../django/db/backends/mysql/introspection.py | 95 + .../django/db/backends/mysql_old/__init__.py | 0 .../django/django/db/backends/mysql_old/base.py | 233 + .../django/django/db/backends/mysql_old/client.py | 14 + .../django/db/backends/mysql_old/creation.py | 29 + .../django/db/backends/mysql_old/introspection.py | 95 + .../django/django/db/backends/oracle/__init__.py | 0 .../lib/django/django/db/backends/oracle/base.py | 151 + .../lib/django/django/db/backends/oracle/client.py | 10 + .../django/django/db/backends/oracle/creation.py | 25 + .../django/db/backends/oracle/introspection.py | 50 + .../django/db/backends/postgresql/__init__.py | 0 .../django/django/db/backends/postgresql/base.py | 241 + .../django/django/db/backends/postgresql/client.py | 15 + .../django/db/backends/postgresql/creation.py | 29 + .../django/db/backends/postgresql/introspection.py | 83 + .../db/backends/postgresql_psycopg2/__init__.py | 0 .../django/db/backends/postgresql_psycopg2/base.py | 186 + .../db/backends/postgresql_psycopg2/client.py | 1 + .../db/backends/postgresql_psycopg2/creation.py | 1 + .../backends/postgresql_psycopg2/introspection.py | 83 + .../django/django/db/backends/sqlite3/__init__.py | 0 .../lib/django/django/db/backends/sqlite3/base.py | 201 + .../django/django/db/backends/sqlite3/client.py | 6 + .../django/django/db/backends/sqlite3/creation.py | 28 + .../django/db/backends/sqlite3/introspection.py | 87 + .../lib/django/django/db/backends/util.py | 120 + .../lib/django/django/db/models/__init__.py | 58 + .../lib/django/django/db/models/base.py | 448 + .../lib/django/django/db/models/fields/__init__.py | 892 ++ .../lib/django/django/db/models/fields/generic.py | 260 + .../lib/django/django/db/models/fields/related.py | 801 ++ .../lib/django/django/db/models/loading.py | 116 + .../lib/django/django/db/models/manager.py | 117 + .../lib/django/django/db/models/manipulators.py | 335 + .../lib/django/django/db/models/options.py | 274 + .../lib/django/django/db/models/query.py | 1079 +++ .../lib/django/django/db/models/related.py | 142 + .../lib/django/django/db/models/signals.py | 12 + .../lib/django/django/db/transaction.py | 222 + .../lib/django/django/dispatch/__init__.py | 6 + .../lib/django/django/dispatch/dispatcher.py | 495 ++ .../lib/django/django/dispatch/errors.py | 10 + .../lib/django/django/dispatch/robust.py | 57 + .../lib/django/django/dispatch/robustapply.py | 47 + .../lib/django/django/dispatch/saferef.py | 165 + .../lib/django/django/forms/__init__.py | 1 + .../lib/django/django/http/__init__.py | 304 + .../lib/django/django/middleware/__init__.py | 0 .../lib/django/django/middleware/cache.py | 84 + .../lib/django/django/middleware/common.py | 96 + .../lib/django/django/middleware/doc.py | 18 + .../lib/django/django/middleware/gzip.py | 29 + .../lib/django/django/middleware/http.py | 61 + .../lib/django/django/middleware/locale.py | 24 + .../lib/django/django/middleware/transaction.py | 27 + .../lib/django/django/newforms/__init__.py | 17 + .../lib/django/django/newforms/extras/__init__.py | 1 + .../lib/django/django/newforms/extras/widgets.py | 59 + .../lib/django/django/newforms/fields.py | 494 ++ .../lib/django/django/newforms/forms.py | 309 + .../lib/django/django/newforms/models.py | 190 + .../lib/django/django/newforms/util.py | 74 + .../lib/django/django/newforms/widgets.py | 353 + .../lib/django/django/oldforms/__init__.py | 1015 +++ .../lib/django/django/shortcuts/__init__.py | 32 + .../lib/django/django/template/__init__.py | 918 ++ .../lib/django/django/template/context.py | 100 + .../lib/django/django/template/defaultfilters.py | 617 ++ .../lib/django/django/template/defaulttags.py | 969 +++ .../lib/django/django/template/loader.py | 118 + .../lib/django/django/template/loader_tags.py | 177 + .../lib/django/django/template/loaders/__init__.py | 0 .../django/template/loaders/app_directories.py | 41 + .../lib/django/django/template/loaders/eggs.py | 25 + .../django/django/template/loaders/filesystem.py | 25 + .../lib/django/django/templatetags/__init__.py | 7 + .../lib/django/django/templatetags/i18n.py | 246 + .../lib/django/django/test/__init__.py | 6 + google_appengine/lib/django/django/test/client.py | 256 + google_appengine/lib/django/django/test/doctest.py | 2669 ++++++ google_appengine/lib/django/django/test/signals.py | 1 + google_appengine/lib/django/django/test/simple.py | 88 + .../lib/django/django/test/testcases.py | 50 + google_appengine/lib/django/django/test/utils.py | 110 + .../lib/django/django/utils/__init__.py | 0 .../lib/django/django/utils/_threading_local.py | 240 + .../lib/django/django/utils/autoreload.py | 94 + google_appengine/lib/django/django/utils/cache.py | 166 + .../lib/django/django/utils/daemonize.py | 55 + .../lib/django/django/utils/datastructures.py | 262 + .../lib/django/django/utils/dateformat.py | 262 + google_appengine/lib/django/django/utils/dates.py | 29 + .../lib/django/django/utils/decorators.py | 33 + .../lib/django/django/utils/feedgenerator.py | 273 + .../lib/django/django/utils/functional.py | 54 + google_appengine/lib/django/django/utils/html.py | 115 + google_appengine/lib/django/django/utils/images.py | 22 + .../lib/django/django/utils/itercompat.py | 31 + .../lib/django/django/utils/simplejson/__init__.py | 252 + .../lib/django/django/utils/simplejson/decoder.py | 273 + .../lib/django/django/utils/simplejson/encoder.py | 331 + .../django/django/utils/simplejson/jsonfilter.py | 40 + .../lib/django/django/utils/simplejson/scanner.py | 63 + .../lib/django/django/utils/stopwords.py | 42 + google_appengine/lib/django/django/utils/synch.py | 88 + .../lib/django/django/utils/termcolors.py | 68 + google_appengine/lib/django/django/utils/text.py | 204 + .../lib/django/django/utils/timesince.py | 57 + .../django/django/utils/translation/__init__.py | 8 + .../django/django/utils/translation/trans_null.py | 30 + .../django/django/utils/translation/trans_real.py | 524 ++ google_appengine/lib/django/django/utils/tzinfo.py | 52 + .../lib/django/django/utils/xmlutils.py | 14 + .../lib/django/django/views/__init__.py | 0 google_appengine/lib/django/django/views/debug.py | 661 ++ .../lib/django/django/views/decorators/__init__.py | 0 .../lib/django/django/views/decorators/cache.py | 42 + .../lib/django/django/views/decorators/gzip.py | 6 + .../lib/django/django/views/decorators/http.py | 34 + .../lib/django/django/views/decorators/vary.py | 35 + .../lib/django/django/views/defaults.py | 89 + .../lib/django/django/views/generic/__init__.py | 0 .../django/django/views/generic/create_update.py | 200 + .../lib/django/django/views/generic/date_based.py | 344 + .../lib/django/django/views/generic/list_detail.py | 131 + .../lib/django/django/views/generic/simple.py | 35 + google_appengine/lib/django/django/views/i18n.py | 172 + google_appengine/lib/django/django/views/static.py | 125 + google_appengine/lib/django/docs/add_ons.txt | 206 + google_appengine/lib/django/docs/admin_css.txt | 173 + google_appengine/lib/django/docs/apache_auth.txt | 71 + google_appengine/lib/django/docs/api_stability.txt | 123 + .../lib/django/docs/authentication.txt | 1024 +++ google_appengine/lib/django/docs/cache.txt | 543 ++ google_appengine/lib/django/docs/contributing.txt | 654 ++ google_appengine/lib/django/docs/csrf.txt | 69 + google_appengine/lib/django/docs/databases.txt | 162 + google_appengine/lib/django/docs/db-api.txt | 1804 ++++ .../lib/django/docs/design_philosophies.txt | 281 + google_appengine/lib/django/docs/distributions.txt | 76 + google_appengine/lib/django/docs/django-admin.txt | 544 ++ google_appengine/lib/django/docs/documentation.txt | 148 + google_appengine/lib/django/docs/email.txt | 176 + google_appengine/lib/django/docs/faq.txt | 669 ++ google_appengine/lib/django/docs/fastcgi.txt | 317 + google_appengine/lib/django/docs/flatpages.txt | 115 + google_appengine/lib/django/docs/forms.txt | 695 ++ google_appengine/lib/django/docs/generic_views.txt | 1076 +++ google_appengine/lib/django/docs/i18n.txt | 765 ++ google_appengine/lib/django/docs/install.txt | 143 + .../lib/django/docs/legacy_databases.txt | 69 + google_appengine/lib/django/docs/middleware.txt | 229 + google_appengine/lib/django/docs/model-api.txt | 1921 +++++ google_appengine/lib/django/docs/modpython.txt | 244 + google_appengine/lib/django/docs/newforms.txt | 875 ++ .../lib/django/docs/outputting_csv.txt | 119 + .../lib/django/docs/outputting_pdf.txt | 153 + google_appengine/lib/django/docs/overview.txt | 301 + google_appengine/lib/django/docs/redirects.txt | 71 + .../lib/django/docs/release_notes_0.95.txt | 126 + .../lib/django/docs/release_notes_0.96.txt | 276 + .../lib/django/docs/request_response.txt | 548 ++ google_appengine/lib/django/docs/serialization.txt | 118 + google_appengine/lib/django/docs/sessions.txt | 313 + google_appengine/lib/django/docs/settings.txt | 1046 +++ google_appengine/lib/django/docs/sitemaps.txt | 321 + google_appengine/lib/django/docs/sites.txt | 322 + google_appengine/lib/django/docs/static_files.txt | 125 + .../lib/django/docs/syndication_feeds.txt | 767 ++ google_appengine/lib/django/docs/templates.txt | 1277 +++ .../lib/django/docs/templates_python.txt | 1195 +++ google_appengine/lib/django/docs/testing.txt | 550 ++ google_appengine/lib/django/docs/transactions.txt | 163 + google_appengine/lib/django/docs/tutorial01.txt | 575 ++ google_appengine/lib/django/docs/tutorial02.txt | 437 + google_appengine/lib/django/docs/tutorial03.txt | 466 + google_appengine/lib/django/docs/tutorial04.txt | 259 + google_appengine/lib/django/docs/url_dispatch.txt | 481 ++ google_appengine/lib/django/scripts/rpm-install.sh | 19 + google_appengine/lib/django/setup.cfg | 4 + google_appengine/lib/django/setup.py | 46 + google_appengine/lib/fancy_urllib/__init__.py | 0 .../lib/fancy_urllib/fancy_urllib/__init__.py | 377 + .../lib/fancy_urllib/fancy_urllib/__init__.pyc | Bin 0 -> 14098 bytes google_appengine/lib/ipaddr/ipaddr/COPYING | 202 + google_appengine/lib/ipaddr/ipaddr/README | 8 + google_appengine/lib/ipaddr/ipaddr/__init__.py | 1854 ++++ google_appengine/lib/ipaddr/ipaddr/__init__.pyc | Bin 0 -> 60152 bytes google_appengine/lib/ipaddr/ipaddr/ipaddr_test.py | 953 ++ google_appengine/lib/ipaddr/ipaddr/setup.py | 36 + google_appengine/lib/webob/LICENSE | 20 + google_appengine/lib/webob/PKG-INFO | 23 + google_appengine/lib/webob/WebOb.egg-info/PKG-INFO | 23 + .../lib/webob/WebOb.egg-info/SOURCES.txt | 41 + .../lib/webob/WebOb.egg-info/dependency_links.txt | 1 + .../lib/webob/WebOb.egg-info/top_level.txt | 1 + google_appengine/lib/webob/WebOb.egg-info/zip-safe | 1 + .../lib/webob/docs/comment-example-code/example.py | 150 + .../lib/webob/docs/comment-example.txt | 414 + google_appengine/lib/webob/docs/differences.txt | 590 ++ google_appengine/lib/webob/docs/file-example.txt | 217 + google_appengine/lib/webob/docs/index.txt | 328 + google_appengine/lib/webob/docs/license.txt | 20 + google_appengine/lib/webob/docs/news.txt | 123 + google_appengine/lib/webob/docs/reference.txt | 1001 +++ google_appengine/lib/webob/docs/test-file.txt | 1 + .../lib/webob/docs/wiki-example-code/example.py | 200 + google_appengine/lib/webob/docs/wiki-example.txt | 693 ++ google_appengine/lib/webob/setup.cfg | 48 + google_appengine/lib/webob/setup.py | 33 + google_appengine/lib/webob/test | 8 + google_appengine/lib/webob/tests/__init__.py | 1 + google_appengine/lib/webob/tests/conftest.py | 4 + google_appengine/lib/webob/tests/test_request.py | 94 + google_appengine/lib/webob/tests/test_request.txt | 317 + google_appengine/lib/webob/tests/test_response.py | 37 + google_appengine/lib/webob/tests/test_response.txt | 254 + google_appengine/lib/webob/webob/__init__.py | 2252 +++++ google_appengine/lib/webob/webob/__init__.pyc | Bin 0 -> 80533 bytes google_appengine/lib/webob/webob/acceptparse.py | 293 + google_appengine/lib/webob/webob/acceptparse.pyc | Bin 0 -> 11438 bytes google_appengine/lib/webob/webob/byterange.py | 295 + google_appengine/lib/webob/webob/byterange.pyc | Bin 0 -> 9540 bytes google_appengine/lib/webob/webob/cachecontrol.py | 165 + google_appengine/lib/webob/webob/cachecontrol.pyc | Bin 0 -> 6907 bytes google_appengine/lib/webob/webob/datastruct.py | 58 + google_appengine/lib/webob/webob/datastruct.pyc | Bin 0 -> 3152 bytes google_appengine/lib/webob/webob/etag.py | 214 + google_appengine/lib/webob/webob/etag.pyc | Bin 0 -> 8588 bytes google_appengine/lib/webob/webob/exc.py | 657 ++ google_appengine/lib/webob/webob/headerdict.py | 110 + google_appengine/lib/webob/webob/headerdict.pyc | Bin 0 -> 3983 bytes google_appengine/lib/webob/webob/multidict.py | 593 ++ google_appengine/lib/webob/webob/multidict.pyc | Bin 0 -> 24370 bytes google_appengine/lib/webob/webob/statusreasons.py | 67 + google_appengine/lib/webob/webob/statusreasons.pyc | Bin 0 -> 1966 bytes google_appengine/lib/webob/webob/updatedict.py | 41 + google_appengine/lib/webob/webob/updatedict.pyc | Bin 0 -> 2408 bytes google_appengine/lib/webob/webob/util/__init__.py | 1 + google_appengine/lib/webob/webob/util/__init__.pyc | Bin 0 -> 158 bytes google_appengine/lib/webob/webob/util/dictmixin.py | 102 + .../lib/webob/webob/util/dictmixin.pyc | Bin 0 -> 4948 bytes google_appengine/lib/webob/webob/util/reversed.py | 4 + google_appengine/lib/webob/webob/util/safegzip.py | 21 + .../lib/webob/webob/util/stringtemplate.py | 128 + google_appengine/lib/yaml/LICENSE | 19 + google_appengine/lib/yaml/PKG-INFO | 28 + google_appengine/lib/yaml/README | 24 + .../lib/yaml/examples/yaml-highlight/yaml_hl.cfg | 115 + .../lib/yaml/examples/yaml-highlight/yaml_hl.py | 114 + google_appengine/lib/yaml/ext/_yaml.c | 9054 ++++++++++++++++++++ google_appengine/lib/yaml/ext/_yaml.h | 3 + google_appengine/lib/yaml/ext/_yaml.pxd | 249 + google_appengine/lib/yaml/ext/_yaml.pyx | 1344 +++ google_appengine/lib/yaml/lib/yaml/__init__.py | 290 + google_appengine/lib/yaml/lib/yaml/__init__.pyc | Bin 0 -> 11677 bytes google_appengine/lib/yaml/lib/yaml/composer.py | 118 + google_appengine/lib/yaml/lib/yaml/composer.pyc | Bin 0 -> 4334 bytes google_appengine/lib/yaml/lib/yaml/constructor.py | 675 ++ google_appengine/lib/yaml/lib/yaml/constructor.pyc | Bin 0 -> 22860 bytes google_appengine/lib/yaml/lib/yaml/cyaml.py | 85 + google_appengine/lib/yaml/lib/yaml/cyaml.pyc | Bin 0 -> 4002 bytes google_appengine/lib/yaml/lib/yaml/dumper.py | 62 + google_appengine/lib/yaml/lib/yaml/dumper.pyc | Bin 0 -> 2698 bytes google_appengine/lib/yaml/lib/yaml/emitter.py | 1163 +++ google_appengine/lib/yaml/lib/yaml/emitter.pyc | Bin 0 -> 33431 bytes google_appengine/lib/yaml/lib/yaml/error.py | 75 + google_appengine/lib/yaml/lib/yaml/error.pyc | Bin 0 -> 3149 bytes google_appengine/lib/yaml/lib/yaml/events.py | 86 + google_appengine/lib/yaml/lib/yaml/events.pyc | Bin 0 -> 5370 bytes google_appengine/lib/yaml/lib/yaml/loader.py | 40 + google_appengine/lib/yaml/lib/yaml/loader.pyc | Bin 0 -> 2009 bytes google_appengine/lib/yaml/lib/yaml/nodes.py | 49 + google_appengine/lib/yaml/lib/yaml/nodes.pyc | Bin 0 -> 2356 bytes google_appengine/lib/yaml/lib/yaml/parser.py | 586 ++ google_appengine/lib/yaml/lib/yaml/parser.pyc | Bin 0 -> 15242 bytes google_appengine/lib/yaml/lib/yaml/reader.py | 225 + google_appengine/lib/yaml/lib/yaml/reader.pyc | Bin 0 -> 7002 bytes google_appengine/lib/yaml/lib/yaml/representer.py | 488 ++ google_appengine/lib/yaml/lib/yaml/representer.pyc | Bin 0 -> 15566 bytes google_appengine/lib/yaml/lib/yaml/resolver.py | 223 + google_appengine/lib/yaml/lib/yaml/resolver.pyc | Bin 0 -> 6872 bytes google_appengine/lib/yaml/lib/yaml/scanner.py | 1456 ++++ google_appengine/lib/yaml/lib/yaml/scanner.pyc | Bin 0 -> 34846 bytes google_appengine/lib/yaml/lib/yaml/serializer.py | 111 + google_appengine/lib/yaml/lib/yaml/serializer.pyc | Bin 0 -> 4550 bytes google_appengine/lib/yaml/lib/yaml/tokens.py | 104 + google_appengine/lib/yaml/lib/yaml/tokens.pyc | Bin 0 -> 7038 bytes google_appengine/lib/yaml/setup.cfg | 28 + google_appengine/lib/yaml/setup.py | 53 + google_appengine/lib/yaml/setup_with_libyaml.py | 31 + google_appengine/new_project_template/app.yaml | 8 + google_appengine/new_project_template/index.yaml | 12 + google_appengine/new_project_template/main.py | 33 + google_appengine/remote_api_shell.py | 69 + google_appengine/templates/logging_console.js | 257 + .../templates/logging_console_footer.html | 4 + .../templates/logging_console_header.html | 71 + .../templates/logging_console_middle.html | 4 + google_appengine/tools/bulkload_client.py | 55 + 1102 files changed, 285485 insertions(+) create mode 100644 google_appengine/BUGS create mode 100644 google_appengine/LICENSE create mode 100644 google_appengine/README create mode 100644 google_appengine/RELEASE_NOTES create mode 100644 google_appengine/VERSION create mode 100755 google_appengine/appcfg.py create mode 100755 google_appengine/bulkload_client.py create mode 100755 google_appengine/bulkloader.py create mode 100644 google_appengine/demos/guestbook/app.yaml create mode 100755 google_appengine/demos/guestbook/guestbook.py create mode 100755 google_appengine/dev_appserver.py create mode 100755 google_appengine/google/__init__.py create mode 100644 google_appengine/google/__init__.pyc create mode 100755 google_appengine/google/appengine/__init__.py create mode 100644 google_appengine/google/appengine/__init__.pyc create mode 100755 google_appengine/google/appengine/api/__init__.py create mode 100644 google_appengine/google/appengine/api/__init__.pyc create mode 100755 google_appengine/google/appengine/api/api_base_pb.py create mode 100644 google_appengine/google/appengine/api/api_base_pb.pyc create mode 100755 google_appengine/google/appengine/api/apiproxy_rpc.py create mode 100644 google_appengine/google/appengine/api/apiproxy_rpc.pyc create mode 100755 google_appengine/google/appengine/api/apiproxy_stub.py create mode 100644 google_appengine/google/appengine/api/apiproxy_stub.pyc create mode 100755 google_appengine/google/appengine/api/apiproxy_stub_map.py create mode 100644 google_appengine/google/appengine/api/apiproxy_stub_map.pyc create mode 100755 google_appengine/google/appengine/api/app_logging.py create mode 100755 google_appengine/google/appengine/api/appinfo.py create mode 100644 google_appengine/google/appengine/api/appinfo.pyc create mode 100755 google_appengine/google/appengine/api/appinfo_errors.py create mode 100644 google_appengine/google/appengine/api/appinfo_errors.pyc create mode 100644 google_appengine/google/appengine/api/blobstore/__init__.py create mode 100644 google_appengine/google/appengine/api/blobstore/__init__.pyc create mode 100755 google_appengine/google/appengine/api/blobstore/blobstore.py create mode 100644 google_appengine/google/appengine/api/blobstore/blobstore.pyc create mode 100755 google_appengine/google/appengine/api/blobstore/blobstore_service_pb.py create mode 100644 google_appengine/google/appengine/api/blobstore/blobstore_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/blobstore/blobstore_stub.py create mode 100644 google_appengine/google/appengine/api/blobstore/blobstore_stub.pyc create mode 100755 google_appengine/google/appengine/api/blobstore/file_blob_storage.py create mode 100644 google_appengine/google/appengine/api/blobstore/file_blob_storage.pyc create mode 100755 google_appengine/google/appengine/api/capabilities/__init__.py create mode 100644 google_appengine/google/appengine/api/capabilities/__init__.pyc create mode 100755 google_appengine/google/appengine/api/capabilities/capability_service_pb.py create mode 100644 google_appengine/google/appengine/api/capabilities/capability_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/capabilities/capability_stub.py create mode 100644 google_appengine/google/appengine/api/capabilities/capability_stub.pyc create mode 100755 google_appengine/google/appengine/api/channel/__init__.py create mode 100644 google_appengine/google/appengine/api/channel/__init__.pyc create mode 100755 google_appengine/google/appengine/api/channel/channel.py create mode 100644 google_appengine/google/appengine/api/channel/channel.pyc create mode 100755 google_appengine/google/appengine/api/channel/channel_service_pb.py create mode 100644 google_appengine/google/appengine/api/channel/channel_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/channel/channel_service_stub.py create mode 100644 google_appengine/google/appengine/api/channel/channel_service_stub.pyc create mode 100755 google_appengine/google/appengine/api/croninfo.py create mode 100644 google_appengine/google/appengine/api/croninfo.pyc create mode 100755 google_appengine/google/appengine/api/datastore.py create mode 100644 google_appengine/google/appengine/api/datastore.pyc create mode 100755 google_appengine/google/appengine/api/datastore_admin.py create mode 100644 google_appengine/google/appengine/api/datastore_admin.pyc create mode 100755 google_appengine/google/appengine/api/datastore_entities.py create mode 100755 google_appengine/google/appengine/api/datastore_errors.py create mode 100644 google_appengine/google/appengine/api/datastore_errors.pyc create mode 100755 google_appengine/google/appengine/api/datastore_file_stub.py create mode 100644 google_appengine/google/appengine/api/datastore_file_stub.pyc create mode 100755 google_appengine/google/appengine/api/datastore_types.py create mode 100644 google_appengine/google/appengine/api/datastore_types.pyc create mode 100755 google_appengine/google/appengine/api/dosinfo.py create mode 100644 google_appengine/google/appengine/api/dosinfo.pyc create mode 100755 google_appengine/google/appengine/api/images/__init__.py create mode 100644 google_appengine/google/appengine/api/images/__init__.pyc create mode 100755 google_appengine/google/appengine/api/images/images_not_implemented_stub.py create mode 100755 google_appengine/google/appengine/api/images/images_service_pb.py create mode 100644 google_appengine/google/appengine/api/images/images_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/images/images_stub.py create mode 100644 google_appengine/google/appengine/api/images/images_stub.pyc create mode 100755 google_appengine/google/appengine/api/labs/__init__.py create mode 100644 google_appengine/google/appengine/api/labs/__init__.pyc create mode 100644 google_appengine/google/appengine/api/labs/taskqueue/__init__.py create mode 100644 google_appengine/google/appengine/api/labs/taskqueue/__init__.pyc create mode 100755 google_appengine/google/appengine/api/labs/taskqueue/taskqueue.py create mode 100644 google_appengine/google/appengine/api/labs/taskqueue/taskqueue.pyc create mode 100755 google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py create mode 100644 google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py create mode 100644 google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.pyc create mode 100755 google_appengine/google/appengine/api/lib_config.py create mode 100644 google_appengine/google/appengine/api/lib_config.pyc create mode 100755 google_appengine/google/appengine/api/mail.py create mode 100644 google_appengine/google/appengine/api/mail.pyc create mode 100755 google_appengine/google/appengine/api/mail_errors.py create mode 100644 google_appengine/google/appengine/api/mail_errors.pyc create mode 100755 google_appengine/google/appengine/api/mail_service_pb.py create mode 100644 google_appengine/google/appengine/api/mail_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/mail_stub.py create mode 100644 google_appengine/google/appengine/api/mail_stub.pyc create mode 100755 google_appengine/google/appengine/api/memcache/__init__.py create mode 100644 google_appengine/google/appengine/api/memcache/__init__.pyc create mode 100755 google_appengine/google/appengine/api/memcache/memcache_service_pb.py create mode 100644 google_appengine/google/appengine/api/memcache/memcache_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/memcache/memcache_stub.py create mode 100644 google_appengine/google/appengine/api/memcache/memcache_stub.pyc create mode 100755 google_appengine/google/appengine/api/namespace_manager/__init__.py create mode 100644 google_appengine/google/appengine/api/namespace_manager/__init__.pyc create mode 100755 google_appengine/google/appengine/api/namespace_manager/namespace_manager.py create mode 100644 google_appengine/google/appengine/api/namespace_manager/namespace_manager.pyc create mode 100755 google_appengine/google/appengine/api/oauth/__init__.py create mode 100755 google_appengine/google/appengine/api/oauth/oauth_api.py create mode 100755 google_appengine/google/appengine/api/queueinfo.py create mode 100644 google_appengine/google/appengine/api/queueinfo.pyc create mode 100755 google_appengine/google/appengine/api/quota.py create mode 100755 google_appengine/google/appengine/api/urlfetch.py create mode 100644 google_appengine/google/appengine/api/urlfetch.pyc create mode 100755 google_appengine/google/appengine/api/urlfetch_errors.py create mode 100644 google_appengine/google/appengine/api/urlfetch_errors.pyc create mode 100755 google_appengine/google/appengine/api/urlfetch_service_pb.py create mode 100644 google_appengine/google/appengine/api/urlfetch_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/urlfetch_stub.py create mode 100644 google_appengine/google/appengine/api/urlfetch_stub.pyc create mode 100755 google_appengine/google/appengine/api/user_service_pb.py create mode 100644 google_appengine/google/appengine/api/user_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/user_service_stub.py create mode 100644 google_appengine/google/appengine/api/user_service_stub.pyc create mode 100755 google_appengine/google/appengine/api/users.py create mode 100644 google_appengine/google/appengine/api/users.pyc create mode 100755 google_appengine/google/appengine/api/validation.py create mode 100644 google_appengine/google/appengine/api/validation.pyc create mode 100755 google_appengine/google/appengine/api/xmpp/__init__.py create mode 100644 google_appengine/google/appengine/api/xmpp/__init__.pyc create mode 100755 google_appengine/google/appengine/api/xmpp/xmpp_service_pb.py create mode 100644 google_appengine/google/appengine/api/xmpp/xmpp_service_pb.pyc create mode 100755 google_appengine/google/appengine/api/xmpp/xmpp_service_stub.py create mode 100644 google_appengine/google/appengine/api/xmpp/xmpp_service_stub.pyc create mode 100755 google_appengine/google/appengine/api/yaml_builder.py create mode 100644 google_appengine/google/appengine/api/yaml_builder.pyc create mode 100755 google_appengine/google/appengine/api/yaml_errors.py create mode 100644 google_appengine/google/appengine/api/yaml_errors.pyc create mode 100755 google_appengine/google/appengine/api/yaml_listener.py create mode 100644 google_appengine/google/appengine/api/yaml_listener.pyc create mode 100755 google_appengine/google/appengine/api/yaml_object.py create mode 100644 google_appengine/google/appengine/api/yaml_object.pyc create mode 100755 google_appengine/google/appengine/base/__init__.py create mode 100644 google_appengine/google/appengine/base/__init__.pyc create mode 100755 google_appengine/google/appengine/base/capabilities_pb.py create mode 100644 google_appengine/google/appengine/base/capabilities_pb.pyc create mode 100755 google_appengine/google/appengine/cron/GrocLexer.py create mode 100644 google_appengine/google/appengine/cron/GrocLexer.pyc create mode 100755 google_appengine/google/appengine/cron/GrocParser.py create mode 100644 google_appengine/google/appengine/cron/GrocParser.pyc create mode 100755 google_appengine/google/appengine/cron/__init__.py create mode 100644 google_appengine/google/appengine/cron/__init__.pyc create mode 100755 google_appengine/google/appengine/cron/groc.py create mode 100644 google_appengine/google/appengine/cron/groc.pyc create mode 100755 google_appengine/google/appengine/cron/groctimespecification.py create mode 100644 google_appengine/google/appengine/cron/groctimespecification.pyc create mode 100755 google_appengine/google/appengine/datastore/__init__.py create mode 100644 google_appengine/google/appengine/datastore/__init__.pyc create mode 100755 google_appengine/google/appengine/datastore/action_pb.py create mode 100644 google_appengine/google/appengine/datastore/action_pb.pyc create mode 100755 google_appengine/google/appengine/datastore/datastore_index.py create mode 100644 google_appengine/google/appengine/datastore/datastore_index.pyc create mode 100755 google_appengine/google/appengine/datastore/datastore_pb.py create mode 100644 google_appengine/google/appengine/datastore/datastore_pb.pyc create mode 100644 google_appengine/google/appengine/datastore/datastore_sqlite_stub.py create mode 100644 google_appengine/google/appengine/datastore/datastore_sqlite_stub.pyc create mode 100755 google_appengine/google/appengine/datastore/datastore_v3_pb.py create mode 100644 google_appengine/google/appengine/datastore/datastore_v3_pb.pyc create mode 100755 google_appengine/google/appengine/datastore/entity_pb.py create mode 100644 google_appengine/google/appengine/datastore/entity_pb.pyc create mode 100644 google_appengine/google/appengine/datastore/sortable_pb_encoder.py create mode 100644 google_appengine/google/appengine/datastore/sortable_pb_encoder.pyc create mode 100755 google_appengine/google/appengine/dist/__init__.py create mode 100644 google_appengine/google/appengine/dist/__init__.pyc create mode 100755 google_appengine/google/appengine/dist/_library.py create mode 100644 google_appengine/google/appengine/dist/_library.pyc create mode 100755 google_appengine/google/appengine/dist/ftplib.py create mode 100755 google_appengine/google/appengine/dist/httplib.py create mode 100755 google_appengine/google/appengine/dist/neo_cgi.py create mode 100755 google_appengine/google/appengine/dist/py_imp.py create mode 100755 google_appengine/google/appengine/dist/py_zipimport.py create mode 100644 google_appengine/google/appengine/dist/py_zipimport.pyc create mode 100755 google_appengine/google/appengine/dist/select.py create mode 100755 google_appengine/google/appengine/dist/socket.py create mode 100755 google_appengine/google/appengine/dist/subprocess.py create mode 100755 google_appengine/google/appengine/dist/tempfile.py create mode 100755 google_appengine/google/appengine/ext/__init__.py create mode 100644 google_appengine/google/appengine/ext/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/admin/__init__.py create mode 100644 google_appengine/google/appengine/ext/admin/templates/base.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/cron.html create mode 100755 google_appengine/google/appengine/ext/admin/templates/css/ae.css create mode 100755 google_appengine/google/appengine/ext/admin/templates/css/base.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/cron.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/datastore.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/form.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/inboundmail.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/memcache.css create mode 100755 google_appengine/google/appengine/ext/admin/templates/css/nav.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/pager.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/queues.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/tasks.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/css/xmpp.css create mode 100644 google_appengine/google/appengine/ext/admin/templates/datastore.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/datastore_edit.html create mode 100755 google_appengine/google/appengine/ext/admin/templates/images/google.gif create mode 100644 google_appengine/google/appengine/ext/admin/templates/inboundmail.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/interactive-output.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/interactive.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/js/multipart_form_data.js create mode 100644 google_appengine/google/appengine/ext/admin/templates/js/rfc822_date.js create mode 100644 google_appengine/google/appengine/ext/admin/templates/js/webhook.js create mode 100644 google_appengine/google/appengine/ext/admin/templates/memcache.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/pager.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/queues.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/tasks.html create mode 100644 google_appengine/google/appengine/ext/admin/templates/xmpp.html create mode 100755 google_appengine/google/appengine/ext/appstats/__init__.py create mode 100755 google_appengine/google/appengine/ext/appstats/datamodel_pb.py create mode 100755 google_appengine/google/appengine/ext/appstats/formatting.py create mode 100755 google_appengine/google/appengine/ext/appstats/recording.py create mode 100755 google_appengine/google/appengine/ext/appstats/sample_appengine_config.py create mode 100644 google_appengine/google/appengine/ext/appstats/static/app_engine_logo_sm.gif create mode 100755 google_appengine/google/appengine/ext/appstats/static/appstats_css.css create mode 100755 google_appengine/google/appengine/ext/appstats/static/appstats_js.js create mode 100644 google_appengine/google/appengine/ext/appstats/static/gantt.js create mode 100644 google_appengine/google/appengine/ext/appstats/static/minus.gif create mode 100644 google_appengine/google/appengine/ext/appstats/static/pix.gif create mode 100644 google_appengine/google/appengine/ext/appstats/static/plus.gif create mode 100644 google_appengine/google/appengine/ext/appstats/templates/base.html create mode 100644 google_appengine/google/appengine/ext/appstats/templates/details.html create mode 100644 google_appengine/google/appengine/ext/appstats/templates/file.html create mode 100644 google_appengine/google/appengine/ext/appstats/templates/main.html create mode 100644 google_appengine/google/appengine/ext/appstats/templates/test.html create mode 100755 google_appengine/google/appengine/ext/appstats/ui.py create mode 100644 google_appengine/google/appengine/ext/blobstore/__init__.py create mode 100755 google_appengine/google/appengine/ext/blobstore/blobstore.py create mode 100755 google_appengine/google/appengine/ext/bulkload/__init__.py create mode 100644 google_appengine/google/appengine/ext/bulkload/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.py create mode 100644 google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkloader_config.py create mode 100644 google_appengine/google/appengine/ext/bulkload/bulkloader_config.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkloader_errors.py create mode 100644 google_appengine/google/appengine/ext/bulkload/bulkloader_errors.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkloader_parser.py create mode 100644 google_appengine/google/appengine/ext/bulkload/bulkloader_parser.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.py create mode 100755 google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.yaml create mode 100755 google_appengine/google/appengine/ext/bulkload/connector_interface.py create mode 100644 google_appengine/google/appengine/ext/bulkload/connector_interface.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/constants.py create mode 100644 google_appengine/google/appengine/ext/bulkload/constants.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/csv_connector.py create mode 100644 google_appengine/google/appengine/ext/bulkload/csv_connector.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/simpletext_connector.py create mode 100644 google_appengine/google/appengine/ext/bulkload/simpletext_connector.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/simplexml_connector.py create mode 100644 google_appengine/google/appengine/ext/bulkload/simplexml_connector.pyc create mode 100755 google_appengine/google/appengine/ext/bulkload/transform.py create mode 100755 google_appengine/google/appengine/ext/db/__init__.py create mode 100644 google_appengine/google/appengine/ext/db/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/db/djangoforms.py create mode 100755 google_appengine/google/appengine/ext/db/polymodel.py create mode 100644 google_appengine/google/appengine/ext/db/polymodel.pyc create mode 100755 google_appengine/google/appengine/ext/db/stats.py create mode 100644 google_appengine/google/appengine/ext/db/stats.pyc create mode 100755 google_appengine/google/appengine/ext/deferred/__init__.py create mode 100755 google_appengine/google/appengine/ext/deferred/deferred.py create mode 100755 google_appengine/google/appengine/ext/deferred/handler.py create mode 100755 google_appengine/google/appengine/ext/ereporter/__init__.py create mode 100755 google_appengine/google/appengine/ext/ereporter/ereporter.py create mode 100755 google_appengine/google/appengine/ext/ereporter/report_generator.py create mode 100644 google_appengine/google/appengine/ext/ereporter/templates/report.html create mode 100755 google_appengine/google/appengine/ext/gql/__init__.py create mode 100755 google_appengine/google/appengine/ext/key_range/__init__.py create mode 100644 google_appengine/google/appengine/ext/key_range/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/preload/__init__.py create mode 100644 google_appengine/google/appengine/ext/remote_api/__init__.py create mode 100644 google_appengine/google/appengine/ext/remote_api/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/remote_api/handler.py create mode 100755 google_appengine/google/appengine/ext/remote_api/remote_api_pb.py create mode 100644 google_appengine/google/appengine/ext/remote_api/remote_api_pb.pyc create mode 100755 google_appengine/google/appengine/ext/remote_api/remote_api_services.py create mode 100644 google_appengine/google/appengine/ext/remote_api/remote_api_services.pyc create mode 100755 google_appengine/google/appengine/ext/remote_api/remote_api_stub.py create mode 100644 google_appengine/google/appengine/ext/remote_api/remote_api_stub.pyc create mode 100755 google_appengine/google/appengine/ext/remote_api/throttle.py create mode 100644 google_appengine/google/appengine/ext/remote_api/throttle.pyc create mode 100755 google_appengine/google/appengine/ext/search/__init__.py create mode 100644 google_appengine/google/appengine/ext/search/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/webapp/__init__.py create mode 100644 google_appengine/google/appengine/ext/webapp/__init__.pyc create mode 100755 google_appengine/google/appengine/ext/webapp/blobstore_handlers.py create mode 100755 google_appengine/google/appengine/ext/webapp/mail_handlers.py create mode 100755 google_appengine/google/appengine/ext/webapp/template.py create mode 100755 google_appengine/google/appengine/ext/webapp/util.py create mode 100755 google_appengine/google/appengine/ext/webapp/xmpp_handlers.py create mode 100755 google_appengine/google/appengine/ext/zipserve/__init__.py create mode 100755 google_appengine/google/appengine/runtime/__init__.py create mode 100644 google_appengine/google/appengine/runtime/__init__.pyc create mode 100755 google_appengine/google/appengine/runtime/apiproxy.py create mode 100644 google_appengine/google/appengine/runtime/apiproxy.pyc create mode 100755 google_appengine/google/appengine/runtime/apiproxy_errors.py create mode 100644 google_appengine/google/appengine/runtime/apiproxy_errors.pyc create mode 100755 google_appengine/google/appengine/tools/__init__.py create mode 100644 google_appengine/google/appengine/tools/__init__.pyc create mode 100755 google_appengine/google/appengine/tools/adaptive_thread_pool.py create mode 100644 google_appengine/google/appengine/tools/adaptive_thread_pool.pyc create mode 100755 google_appengine/google/appengine/tools/appcfg.py create mode 100644 google_appengine/google/appengine/tools/appcfg.pyc create mode 100755 google_appengine/google/appengine/tools/appengine_rpc.py create mode 100644 google_appengine/google/appengine/tools/appengine_rpc.pyc create mode 100755 google_appengine/google/appengine/tools/bulkload_client.py create mode 100755 google_appengine/google/appengine/tools/bulkloader.py create mode 100644 google_appengine/google/appengine/tools/bulkloader.pyc create mode 100755 google_appengine/google/appengine/tools/dev-channel-js.js create mode 100755 google_appengine/google/appengine/tools/dev_appserver.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_blobimage.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_blobimage.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_blobstore.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_blobstore.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_channel.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_channel.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_index.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_index.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_login.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_login.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_main.py create mode 100755 google_appengine/google/appengine/tools/dev_appserver_oauth.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_oauth.pyc create mode 100755 google_appengine/google/appengine/tools/dev_appserver_upload.py create mode 100644 google_appengine/google/appengine/tools/dev_appserver_upload.pyc create mode 100755 google_appengine/google/appengine/tools/os_compat.py create mode 100644 google_appengine/google/appengine/tools/os_compat.pyc create mode 100755 google_appengine/google/appengine/tools/remote_api_shell.py create mode 100755 google_appengine/google/appengine/tools/requeue.py create mode 100644 google_appengine/google/appengine/tools/requeue.pyc create mode 100755 google_appengine/google/net/__init__.py create mode 100644 google_appengine/google/net/__init__.pyc create mode 100644 google_appengine/google/net/proto/ProtocolBuffer.py create mode 100644 google_appengine/google/net/proto/ProtocolBuffer.pyc create mode 100755 google_appengine/google/net/proto/RawMessage.py create mode 100644 google_appengine/google/net/proto/RawMessage.pyc create mode 100755 google_appengine/google/net/proto/__init__.py create mode 100644 google_appengine/google/net/proto/__init__.pyc create mode 100755 google_appengine/google/net/proto/message_set.py create mode 100644 google_appengine/google/net/proto/message_set.pyc create mode 100755 google_appengine/google/pyglib/__init__.py create mode 100644 google_appengine/google/pyglib/__init__.pyc create mode 100644 google_appengine/google/pyglib/gexcept.py create mode 100644 google_appengine/google/pyglib/gexcept.pyc create mode 100755 google_appengine/lib/antlr3/AUTHORS create mode 100755 google_appengine/lib/antlr3/LICENSE create mode 100755 google_appengine/lib/antlr3/MANIFEST.in create mode 100644 google_appengine/lib/antlr3/OWNERS create mode 100755 google_appengine/lib/antlr3/README create mode 100755 google_appengine/lib/antlr3/antlr3/__init__.py create mode 100644 google_appengine/lib/antlr3/antlr3/__init__.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/compat.py create mode 100644 google_appengine/lib/antlr3/antlr3/compat.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/constants.py create mode 100644 google_appengine/lib/antlr3/antlr3/constants.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/dfa.py create mode 100644 google_appengine/lib/antlr3/antlr3/dfa.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/dottreegen.py create mode 100755 google_appengine/lib/antlr3/antlr3/exceptions.py create mode 100644 google_appengine/lib/antlr3/antlr3/exceptions.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/extras.py create mode 100755 google_appengine/lib/antlr3/antlr3/main.py create mode 100755 google_appengine/lib/antlr3/antlr3/recognizers.py create mode 100644 google_appengine/lib/antlr3/antlr3/recognizers.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/streams.py create mode 100644 google_appengine/lib/antlr3/antlr3/streams.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/tokens.py create mode 100644 google_appengine/lib/antlr3/antlr3/tokens.pyc create mode 100755 google_appengine/lib/antlr3/antlr3/tree.py create mode 100755 google_appengine/lib/antlr3/antlr3/treewizard.py create mode 100755 google_appengine/lib/antlr3/antlr_python_runtime.egg-info/PKG-INFO create mode 100644 google_appengine/lib/antlr3/antlr_python_runtime.egg-info/SOURCES.txt create mode 100644 google_appengine/lib/antlr3/antlr_python_runtime.egg-info/dependency_links.txt create mode 100644 google_appengine/lib/antlr3/antlr_python_runtime.egg-info/top_level.txt create mode 100755 google_appengine/lib/antlr3/setup.py create mode 100644 google_appengine/lib/cacerts/cacerts.txt create mode 100644 google_appengine/lib/django/AUTHORS create mode 100644 google_appengine/lib/django/INSTALL create mode 100644 google_appengine/lib/django/LICENSE create mode 100644 google_appengine/lib/django/PKG-INFO create mode 100644 google_appengine/lib/django/README create mode 100755 google_appengine/lib/django/django/__init__.py create mode 100755 google_appengine/lib/django/django/bin/__init__.py create mode 100755 google_appengine/lib/django/django/bin/compile-messages.py create mode 100755 google_appengine/lib/django/django/bin/daily_cleanup.py create mode 100755 google_appengine/lib/django/django/bin/django-admin.py create mode 100755 google_appengine/lib/django/django/bin/make-messages.py create mode 100755 google_appengine/lib/django/django/bin/profiling/__init__.py create mode 100755 google_appengine/lib/django/django/bin/profiling/gather_profile_stats.py create mode 100755 google_appengine/lib/django/django/bin/unique-messages.py create mode 100755 google_appengine/lib/django/django/conf/__init__.py create mode 100755 google_appengine/lib/django/django/conf/app_template/__init__.py create mode 100755 google_appengine/lib/django/django/conf/app_template/models.py create mode 100755 google_appengine/lib/django/django/conf/app_template/views.py create mode 100755 google_appengine/lib/django/django/conf/global_settings.py create mode 100644 google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ru/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sk/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sl/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sr/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/sv/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/ta/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/te/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/tr/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/uk/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.mo create mode 100644 google_appengine/lib/django/django/conf/locale/zh_CN/LC_MESSAGES/djangojs.po create mode 100644 google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.mo create mode 100644 google_appengine/lib/django/django/conf/locale/zh_TW/LC_MESSAGES/django.po create mode 100755 google_appengine/lib/django/django/conf/project_template/__init__.py create mode 100755 google_appengine/lib/django/django/conf/project_template/manage.py create mode 100755 google_appengine/lib/django/django/conf/project_template/settings.py create mode 100755 google_appengine/lib/django/django/conf/project_template/urls.py create mode 100755 google_appengine/lib/django/django/conf/urls/__init__.py create mode 100755 google_appengine/lib/django/django/conf/urls/defaults.py create mode 100755 google_appengine/lib/django/django/conf/urls/i18n.py create mode 100755 google_appengine/lib/django/django/conf/urls/shortcut.py create mode 100755 google_appengine/lib/django/django/contrib/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/admin/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/admin/filterspecs.py create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/base.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/changelists.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/dashboard.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/forms.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/global.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/layout.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/login.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/patch-iewin.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/rtl.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/css/widgets.css create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-down.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/arrow-up.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/changelist-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/chooser_stacked-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg-reverse.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/default-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/deleted-overlay.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-no.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-unknown.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon-yes.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_addlink.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_alert.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_calendar.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_changelink.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_clock.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_deletelink.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_error.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_searchbox.png create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/icon_success.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete-8bit.png create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-delete.png create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore-8bit.png create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-restore.png create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/inline-splitter-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-grabber.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg-reverse.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/nav-bg.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-add.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-addall.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-remove.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-removeall.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector-search.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-add.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/selector_stacked-remove.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-left_over.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tool-right_over.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-add_over.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/img/admin/tooltag-arrowright_over.gif create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/SelectBox.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/SelectFilter2.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/admin/CollapsedFieldsets.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/admin/DateTimeShortcuts.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/admin/RelatedObjectLookups.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/admin/ordering.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/calendar.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/core.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/dateparse.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/getElementsBySelector.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/timeparse.js create mode 100644 google_appengine/lib/django/django/contrib/admin/media/js/urlify.js create mode 100755 google_appengine/lib/django/django/contrib/admin/models.py create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/404.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/500.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/add_form.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/auth/user/change_password.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/base.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/base_site.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/change_form.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/change_list.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/change_list_results.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/date_hierarchy.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/delete_confirmation.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_stacked.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/edit_inline_tabular.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/field_line.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/filter.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/filters.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/invalid_setup.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/login.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/object_history.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/pagination.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/search_form.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/submit_line.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin/template_validator.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/bookmarklets.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/missing_docutils.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_detail.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/model_index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_detail.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_filter_index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/template_tag_index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_detail.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/admin_doc/view_index.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/logged_out.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_done.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/password_change_form.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_done.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_email.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/registration/password_reset_form.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/date_time.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/default.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/file.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/foreign.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/many_to_many.html create mode 100644 google_appengine/lib/django/django/contrib/admin/templates/widget/one_to_one.html create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/admin_list.py create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/admin_modify.py create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/adminapplist.py create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/adminmedia.py create mode 100755 google_appengine/lib/django/django/contrib/admin/templatetags/log.py create mode 100755 google_appengine/lib/django/django/contrib/admin/urls.py create mode 100755 google_appengine/lib/django/django/contrib/admin/utils.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/auth.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/decorators.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/doc.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/main.py create mode 100755 google_appengine/lib/django/django/contrib/admin/views/template.py create mode 100755 google_appengine/lib/django/django/contrib/auth/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/auth/backends.py create mode 100755 google_appengine/lib/django/django/contrib/auth/create_superuser.py create mode 100755 google_appengine/lib/django/django/contrib/auth/decorators.py create mode 100755 google_appengine/lib/django/django/contrib/auth/forms.py create mode 100755 google_appengine/lib/django/django/contrib/auth/handlers/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/auth/handlers/modpython.py create mode 100755 google_appengine/lib/django/django/contrib/auth/management.py create mode 100755 google_appengine/lib/django/django/contrib/auth/middleware.py create mode 100755 google_appengine/lib/django/django/contrib/auth/models.py create mode 100755 google_appengine/lib/django/django/contrib/auth/views.py create mode 100755 google_appengine/lib/django/django/contrib/comments/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/comments/feeds.py create mode 100755 google_appengine/lib/django/django/contrib/comments/models.py create mode 100644 google_appengine/lib/django/django/contrib/comments/templates/comments/form.html create mode 100644 google_appengine/lib/django/django/contrib/comments/templates/comments/freeform.html create mode 100755 google_appengine/lib/django/django/contrib/comments/templatetags/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/comments/templatetags/comments.py create mode 100755 google_appengine/lib/django/django/contrib/comments/urls/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/comments/urls/comments.py create mode 100755 google_appengine/lib/django/django/contrib/comments/views/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/comments/views/comments.py create mode 100755 google_appengine/lib/django/django/contrib/comments/views/karma.py create mode 100755 google_appengine/lib/django/django/contrib/comments/views/userflags.py create mode 100755 google_appengine/lib/django/django/contrib/contenttypes/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/contenttypes/management.py create mode 100755 google_appengine/lib/django/django/contrib/contenttypes/models.py create mode 100755 google_appengine/lib/django/django/contrib/csrf/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/csrf/middleware.py create mode 100755 google_appengine/lib/django/django/contrib/flatpages/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/flatpages/middleware.py create mode 100755 google_appengine/lib/django/django/contrib/flatpages/models.py create mode 100755 google_appengine/lib/django/django/contrib/flatpages/urls.py create mode 100755 google_appengine/lib/django/django/contrib/flatpages/views.py create mode 100755 google_appengine/lib/django/django/contrib/formtools/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/formtools/preview.py create mode 100755 google_appengine/lib/django/django/contrib/humanize/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/humanize/templatetags/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/humanize/templatetags/humanize.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/uk/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/uk/forms.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/usa/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/usa/forms.py create mode 100755 google_appengine/lib/django/django/contrib/localflavor/usa/us_states.py create mode 100755 google_appengine/lib/django/django/contrib/markup/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/markup/templatetags/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/markup/templatetags/markup.py create mode 100755 google_appengine/lib/django/django/contrib/redirects/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/redirects/middleware.py create mode 100755 google_appengine/lib/django/django/contrib/redirects/models.py create mode 100755 google_appengine/lib/django/django/contrib/sessions/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/sessions/middleware.py create mode 100755 google_appengine/lib/django/django/contrib/sessions/models.py create mode 100755 google_appengine/lib/django/django/contrib/sitemaps/__init__.py create mode 100644 google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap.xml create mode 100644 google_appengine/lib/django/django/contrib/sitemaps/templates/sitemap_index.xml create mode 100755 google_appengine/lib/django/django/contrib/sitemaps/views.py create mode 100755 google_appengine/lib/django/django/contrib/sites/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/sites/management.py create mode 100755 google_appengine/lib/django/django/contrib/sites/managers.py create mode 100755 google_appengine/lib/django/django/contrib/sites/models.py create mode 100755 google_appengine/lib/django/django/contrib/syndication/__init__.py create mode 100755 google_appengine/lib/django/django/contrib/syndication/feeds.py create mode 100755 google_appengine/lib/django/django/contrib/syndication/views.py create mode 100755 google_appengine/lib/django/django/core/__init__.py create mode 100755 google_appengine/lib/django/django/core/cache/__init__.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/__init__.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/base.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/db.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/dummy.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/filebased.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/locmem.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/memcached.py create mode 100755 google_appengine/lib/django/django/core/cache/backends/simple.py create mode 100755 google_appengine/lib/django/django/core/context_processors.py create mode 100755 google_appengine/lib/django/django/core/exceptions.py create mode 100755 google_appengine/lib/django/django/core/handler.py create mode 100755 google_appengine/lib/django/django/core/handlers/__init__.py create mode 100755 google_appengine/lib/django/django/core/handlers/base.py create mode 100755 google_appengine/lib/django/django/core/handlers/modpython.py create mode 100755 google_appengine/lib/django/django/core/handlers/profiler-hotshot.py create mode 100755 google_appengine/lib/django/django/core/handlers/wsgi.py create mode 100755 google_appengine/lib/django/django/core/mail.py create mode 100755 google_appengine/lib/django/django/core/management.py create mode 100755 google_appengine/lib/django/django/core/paginator.py create mode 100755 google_appengine/lib/django/django/core/serializers/__init__.py create mode 100755 google_appengine/lib/django/django/core/serializers/base.py create mode 100755 google_appengine/lib/django/django/core/serializers/json.py create mode 100755 google_appengine/lib/django/django/core/serializers/python.py create mode 100755 google_appengine/lib/django/django/core/serializers/pyyaml.py create mode 100755 google_appengine/lib/django/django/core/serializers/xml_serializer.py create mode 100755 google_appengine/lib/django/django/core/servers/__init__.py create mode 100755 google_appengine/lib/django/django/core/servers/basehttp.py create mode 100755 google_appengine/lib/django/django/core/servers/fastcgi.py create mode 100755 google_appengine/lib/django/django/core/signals.py create mode 100755 google_appengine/lib/django/django/core/template_loader.py create mode 100755 google_appengine/lib/django/django/core/urlresolvers.py create mode 100755 google_appengine/lib/django/django/core/validators.py create mode 100755 google_appengine/lib/django/django/core/xheaders.py create mode 100755 google_appengine/lib/django/django/db/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/ado_mssql/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/ado_mssql/base.py create mode 100755 google_appengine/lib/django/django/db/backends/ado_mssql/client.py create mode 100755 google_appengine/lib/django/django/db/backends/ado_mssql/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/ado_mssql/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/dummy/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/dummy/base.py create mode 100755 google_appengine/lib/django/django/db/backends/dummy/client.py create mode 100755 google_appengine/lib/django/django/db/backends/dummy/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/dummy/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql/base.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql/client.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql_old/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql_old/base.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql_old/client.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql_old/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/mysql_old/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/oracle/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/oracle/base.py create mode 100755 google_appengine/lib/django/django/db/backends/oracle/client.py create mode 100755 google_appengine/lib/django/django/db/backends/oracle/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/oracle/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql/base.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql/client.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql_psycopg2/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql_psycopg2/base.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql_psycopg2/client.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql_psycopg2/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/postgresql_psycopg2/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/sqlite3/__init__.py create mode 100755 google_appengine/lib/django/django/db/backends/sqlite3/base.py create mode 100755 google_appengine/lib/django/django/db/backends/sqlite3/client.py create mode 100755 google_appengine/lib/django/django/db/backends/sqlite3/creation.py create mode 100755 google_appengine/lib/django/django/db/backends/sqlite3/introspection.py create mode 100755 google_appengine/lib/django/django/db/backends/util.py create mode 100755 google_appengine/lib/django/django/db/models/__init__.py create mode 100755 google_appengine/lib/django/django/db/models/base.py create mode 100755 google_appengine/lib/django/django/db/models/fields/__init__.py create mode 100755 google_appengine/lib/django/django/db/models/fields/generic.py create mode 100755 google_appengine/lib/django/django/db/models/fields/related.py create mode 100755 google_appengine/lib/django/django/db/models/loading.py create mode 100755 google_appengine/lib/django/django/db/models/manager.py create mode 100755 google_appengine/lib/django/django/db/models/manipulators.py create mode 100755 google_appengine/lib/django/django/db/models/options.py create mode 100755 google_appengine/lib/django/django/db/models/query.py create mode 100755 google_appengine/lib/django/django/db/models/related.py create mode 100755 google_appengine/lib/django/django/db/models/signals.py create mode 100755 google_appengine/lib/django/django/db/transaction.py create mode 100755 google_appengine/lib/django/django/dispatch/__init__.py create mode 100755 google_appengine/lib/django/django/dispatch/dispatcher.py create mode 100755 google_appengine/lib/django/django/dispatch/errors.py create mode 100755 google_appengine/lib/django/django/dispatch/robust.py create mode 100755 google_appengine/lib/django/django/dispatch/robustapply.py create mode 100755 google_appengine/lib/django/django/dispatch/saferef.py create mode 100755 google_appengine/lib/django/django/forms/__init__.py create mode 100755 google_appengine/lib/django/django/http/__init__.py create mode 100755 google_appengine/lib/django/django/middleware/__init__.py create mode 100755 google_appengine/lib/django/django/middleware/cache.py create mode 100755 google_appengine/lib/django/django/middleware/common.py create mode 100755 google_appengine/lib/django/django/middleware/doc.py create mode 100755 google_appengine/lib/django/django/middleware/gzip.py create mode 100755 google_appengine/lib/django/django/middleware/http.py create mode 100755 google_appengine/lib/django/django/middleware/locale.py create mode 100755 google_appengine/lib/django/django/middleware/transaction.py create mode 100755 google_appengine/lib/django/django/newforms/__init__.py create mode 100755 google_appengine/lib/django/django/newforms/extras/__init__.py create mode 100755 google_appengine/lib/django/django/newforms/extras/widgets.py create mode 100755 google_appengine/lib/django/django/newforms/fields.py create mode 100755 google_appengine/lib/django/django/newforms/forms.py create mode 100755 google_appengine/lib/django/django/newforms/models.py create mode 100755 google_appengine/lib/django/django/newforms/util.py create mode 100755 google_appengine/lib/django/django/newforms/widgets.py create mode 100755 google_appengine/lib/django/django/oldforms/__init__.py create mode 100755 google_appengine/lib/django/django/shortcuts/__init__.py create mode 100755 google_appengine/lib/django/django/template/__init__.py create mode 100755 google_appengine/lib/django/django/template/context.py create mode 100755 google_appengine/lib/django/django/template/defaultfilters.py create mode 100755 google_appengine/lib/django/django/template/defaulttags.py create mode 100755 google_appengine/lib/django/django/template/loader.py create mode 100755 google_appengine/lib/django/django/template/loader_tags.py create mode 100755 google_appengine/lib/django/django/template/loaders/__init__.py create mode 100755 google_appengine/lib/django/django/template/loaders/app_directories.py create mode 100755 google_appengine/lib/django/django/template/loaders/eggs.py create mode 100755 google_appengine/lib/django/django/template/loaders/filesystem.py create mode 100755 google_appengine/lib/django/django/templatetags/__init__.py create mode 100755 google_appengine/lib/django/django/templatetags/i18n.py create mode 100755 google_appengine/lib/django/django/test/__init__.py create mode 100755 google_appengine/lib/django/django/test/client.py create mode 100755 google_appengine/lib/django/django/test/doctest.py create mode 100755 google_appengine/lib/django/django/test/signals.py create mode 100755 google_appengine/lib/django/django/test/simple.py create mode 100755 google_appengine/lib/django/django/test/testcases.py create mode 100755 google_appengine/lib/django/django/test/utils.py create mode 100755 google_appengine/lib/django/django/utils/__init__.py create mode 100755 google_appengine/lib/django/django/utils/_threading_local.py create mode 100755 google_appengine/lib/django/django/utils/autoreload.py create mode 100755 google_appengine/lib/django/django/utils/cache.py create mode 100755 google_appengine/lib/django/django/utils/daemonize.py create mode 100755 google_appengine/lib/django/django/utils/datastructures.py create mode 100755 google_appengine/lib/django/django/utils/dateformat.py create mode 100755 google_appengine/lib/django/django/utils/dates.py create mode 100755 google_appengine/lib/django/django/utils/decorators.py create mode 100755 google_appengine/lib/django/django/utils/feedgenerator.py create mode 100755 google_appengine/lib/django/django/utils/functional.py create mode 100755 google_appengine/lib/django/django/utils/html.py create mode 100755 google_appengine/lib/django/django/utils/images.py create mode 100755 google_appengine/lib/django/django/utils/itercompat.py create mode 100755 google_appengine/lib/django/django/utils/simplejson/__init__.py create mode 100755 google_appengine/lib/django/django/utils/simplejson/decoder.py create mode 100755 google_appengine/lib/django/django/utils/simplejson/encoder.py create mode 100755 google_appengine/lib/django/django/utils/simplejson/jsonfilter.py create mode 100755 google_appengine/lib/django/django/utils/simplejson/scanner.py create mode 100755 google_appengine/lib/django/django/utils/stopwords.py create mode 100755 google_appengine/lib/django/django/utils/synch.py create mode 100755 google_appengine/lib/django/django/utils/termcolors.py create mode 100755 google_appengine/lib/django/django/utils/text.py create mode 100755 google_appengine/lib/django/django/utils/timesince.py create mode 100755 google_appengine/lib/django/django/utils/translation/__init__.py create mode 100755 google_appengine/lib/django/django/utils/translation/trans_null.py create mode 100755 google_appengine/lib/django/django/utils/translation/trans_real.py create mode 100755 google_appengine/lib/django/django/utils/tzinfo.py create mode 100755 google_appengine/lib/django/django/utils/xmlutils.py create mode 100755 google_appengine/lib/django/django/views/__init__.py create mode 100755 google_appengine/lib/django/django/views/debug.py create mode 100755 google_appengine/lib/django/django/views/decorators/__init__.py create mode 100755 google_appengine/lib/django/django/views/decorators/cache.py create mode 100755 google_appengine/lib/django/django/views/decorators/gzip.py create mode 100755 google_appengine/lib/django/django/views/decorators/http.py create mode 100755 google_appengine/lib/django/django/views/decorators/vary.py create mode 100755 google_appengine/lib/django/django/views/defaults.py create mode 100755 google_appengine/lib/django/django/views/generic/__init__.py create mode 100755 google_appengine/lib/django/django/views/generic/create_update.py create mode 100755 google_appengine/lib/django/django/views/generic/date_based.py create mode 100755 google_appengine/lib/django/django/views/generic/list_detail.py create mode 100755 google_appengine/lib/django/django/views/generic/simple.py create mode 100755 google_appengine/lib/django/django/views/i18n.py create mode 100755 google_appengine/lib/django/django/views/static.py create mode 100644 google_appengine/lib/django/docs/add_ons.txt create mode 100644 google_appengine/lib/django/docs/admin_css.txt create mode 100644 google_appengine/lib/django/docs/apache_auth.txt create mode 100644 google_appengine/lib/django/docs/api_stability.txt create mode 100644 google_appengine/lib/django/docs/authentication.txt create mode 100644 google_appengine/lib/django/docs/cache.txt create mode 100644 google_appengine/lib/django/docs/contributing.txt create mode 100644 google_appengine/lib/django/docs/csrf.txt create mode 100644 google_appengine/lib/django/docs/databases.txt create mode 100644 google_appengine/lib/django/docs/db-api.txt create mode 100644 google_appengine/lib/django/docs/design_philosophies.txt create mode 100644 google_appengine/lib/django/docs/distributions.txt create mode 100644 google_appengine/lib/django/docs/django-admin.txt create mode 100644 google_appengine/lib/django/docs/documentation.txt create mode 100644 google_appengine/lib/django/docs/email.txt create mode 100644 google_appengine/lib/django/docs/faq.txt create mode 100644 google_appengine/lib/django/docs/fastcgi.txt create mode 100644 google_appengine/lib/django/docs/flatpages.txt create mode 100644 google_appengine/lib/django/docs/forms.txt create mode 100644 google_appengine/lib/django/docs/generic_views.txt create mode 100644 google_appengine/lib/django/docs/i18n.txt create mode 100644 google_appengine/lib/django/docs/install.txt create mode 100644 google_appengine/lib/django/docs/legacy_databases.txt create mode 100644 google_appengine/lib/django/docs/middleware.txt create mode 100644 google_appengine/lib/django/docs/model-api.txt create mode 100644 google_appengine/lib/django/docs/modpython.txt create mode 100644 google_appengine/lib/django/docs/newforms.txt create mode 100644 google_appengine/lib/django/docs/outputting_csv.txt create mode 100644 google_appengine/lib/django/docs/outputting_pdf.txt create mode 100644 google_appengine/lib/django/docs/overview.txt create mode 100644 google_appengine/lib/django/docs/redirects.txt create mode 100644 google_appengine/lib/django/docs/release_notes_0.95.txt create mode 100644 google_appengine/lib/django/docs/release_notes_0.96.txt create mode 100644 google_appengine/lib/django/docs/request_response.txt create mode 100644 google_appengine/lib/django/docs/serialization.txt create mode 100644 google_appengine/lib/django/docs/sessions.txt create mode 100644 google_appengine/lib/django/docs/settings.txt create mode 100644 google_appengine/lib/django/docs/sitemaps.txt create mode 100644 google_appengine/lib/django/docs/sites.txt create mode 100644 google_appengine/lib/django/docs/static_files.txt create mode 100644 google_appengine/lib/django/docs/syndication_feeds.txt create mode 100644 google_appengine/lib/django/docs/templates.txt create mode 100644 google_appengine/lib/django/docs/templates_python.txt create mode 100644 google_appengine/lib/django/docs/testing.txt create mode 100644 google_appengine/lib/django/docs/transactions.txt create mode 100644 google_appengine/lib/django/docs/tutorial01.txt create mode 100644 google_appengine/lib/django/docs/tutorial02.txt create mode 100644 google_appengine/lib/django/docs/tutorial03.txt create mode 100644 google_appengine/lib/django/docs/tutorial04.txt create mode 100644 google_appengine/lib/django/docs/url_dispatch.txt create mode 100644 google_appengine/lib/django/scripts/rpm-install.sh create mode 100644 google_appengine/lib/django/setup.cfg create mode 100755 google_appengine/lib/django/setup.py create mode 100755 google_appengine/lib/fancy_urllib/__init__.py create mode 100755 google_appengine/lib/fancy_urllib/fancy_urllib/__init__.py create mode 100644 google_appengine/lib/fancy_urllib/fancy_urllib/__init__.pyc create mode 100644 google_appengine/lib/ipaddr/ipaddr/COPYING create mode 100644 google_appengine/lib/ipaddr/ipaddr/README create mode 100644 google_appengine/lib/ipaddr/ipaddr/__init__.py create mode 100644 google_appengine/lib/ipaddr/ipaddr/__init__.pyc create mode 100755 google_appengine/lib/ipaddr/ipaddr/ipaddr_test.py create mode 100755 google_appengine/lib/ipaddr/ipaddr/setup.py create mode 100644 google_appengine/lib/webob/LICENSE create mode 100644 google_appengine/lib/webob/PKG-INFO create mode 100644 google_appengine/lib/webob/WebOb.egg-info/PKG-INFO create mode 100644 google_appengine/lib/webob/WebOb.egg-info/SOURCES.txt create mode 100644 google_appengine/lib/webob/WebOb.egg-info/dependency_links.txt create mode 100644 google_appengine/lib/webob/WebOb.egg-info/top_level.txt create mode 100644 google_appengine/lib/webob/WebOb.egg-info/zip-safe create mode 100755 google_appengine/lib/webob/docs/comment-example-code/example.py create mode 100644 google_appengine/lib/webob/docs/comment-example.txt create mode 100644 google_appengine/lib/webob/docs/differences.txt create mode 100644 google_appengine/lib/webob/docs/file-example.txt create mode 100644 google_appengine/lib/webob/docs/index.txt create mode 100644 google_appengine/lib/webob/docs/license.txt create mode 100644 google_appengine/lib/webob/docs/news.txt create mode 100644 google_appengine/lib/webob/docs/reference.txt create mode 100644 google_appengine/lib/webob/docs/test-file.txt create mode 100755 google_appengine/lib/webob/docs/wiki-example-code/example.py create mode 100644 google_appengine/lib/webob/docs/wiki-example.txt create mode 100644 google_appengine/lib/webob/setup.cfg create mode 100755 google_appengine/lib/webob/setup.py create mode 100755 google_appengine/lib/webob/test create mode 100755 google_appengine/lib/webob/tests/__init__.py create mode 100755 google_appengine/lib/webob/tests/conftest.py create mode 100755 google_appengine/lib/webob/tests/test_request.py create mode 100644 google_appengine/lib/webob/tests/test_request.txt create mode 100755 google_appengine/lib/webob/tests/test_response.py create mode 100644 google_appengine/lib/webob/tests/test_response.txt create mode 100755 google_appengine/lib/webob/webob/__init__.py create mode 100644 google_appengine/lib/webob/webob/__init__.pyc create mode 100755 google_appengine/lib/webob/webob/acceptparse.py create mode 100644 google_appengine/lib/webob/webob/acceptparse.pyc create mode 100755 google_appengine/lib/webob/webob/byterange.py create mode 100644 google_appengine/lib/webob/webob/byterange.pyc create mode 100755 google_appengine/lib/webob/webob/cachecontrol.py create mode 100644 google_appengine/lib/webob/webob/cachecontrol.pyc create mode 100755 google_appengine/lib/webob/webob/datastruct.py create mode 100644 google_appengine/lib/webob/webob/datastruct.pyc create mode 100755 google_appengine/lib/webob/webob/etag.py create mode 100644 google_appengine/lib/webob/webob/etag.pyc create mode 100755 google_appengine/lib/webob/webob/exc.py create mode 100755 google_appengine/lib/webob/webob/headerdict.py create mode 100644 google_appengine/lib/webob/webob/headerdict.pyc create mode 100755 google_appengine/lib/webob/webob/multidict.py create mode 100644 google_appengine/lib/webob/webob/multidict.pyc create mode 100755 google_appengine/lib/webob/webob/statusreasons.py create mode 100644 google_appengine/lib/webob/webob/statusreasons.pyc create mode 100755 google_appengine/lib/webob/webob/updatedict.py create mode 100644 google_appengine/lib/webob/webob/updatedict.pyc create mode 100755 google_appengine/lib/webob/webob/util/__init__.py create mode 100644 google_appengine/lib/webob/webob/util/__init__.pyc create mode 100755 google_appengine/lib/webob/webob/util/dictmixin.py create mode 100644 google_appengine/lib/webob/webob/util/dictmixin.pyc create mode 100755 google_appengine/lib/webob/webob/util/reversed.py create mode 100755 google_appengine/lib/webob/webob/util/safegzip.py create mode 100755 google_appengine/lib/webob/webob/util/stringtemplate.py create mode 100644 google_appengine/lib/yaml/LICENSE create mode 100644 google_appengine/lib/yaml/PKG-INFO create mode 100644 google_appengine/lib/yaml/README create mode 100644 google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.cfg create mode 100755 google_appengine/lib/yaml/examples/yaml-highlight/yaml_hl.py create mode 100644 google_appengine/lib/yaml/ext/_yaml.c create mode 100644 google_appengine/lib/yaml/ext/_yaml.h create mode 100644 google_appengine/lib/yaml/ext/_yaml.pxd create mode 100644 google_appengine/lib/yaml/ext/_yaml.pyx create mode 100755 google_appengine/lib/yaml/lib/yaml/__init__.py create mode 100644 google_appengine/lib/yaml/lib/yaml/__init__.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/composer.py create mode 100644 google_appengine/lib/yaml/lib/yaml/composer.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/constructor.py create mode 100644 google_appengine/lib/yaml/lib/yaml/constructor.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/cyaml.py create mode 100644 google_appengine/lib/yaml/lib/yaml/cyaml.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/dumper.py create mode 100644 google_appengine/lib/yaml/lib/yaml/dumper.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/emitter.py create mode 100644 google_appengine/lib/yaml/lib/yaml/emitter.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/error.py create mode 100644 google_appengine/lib/yaml/lib/yaml/error.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/events.py create mode 100644 google_appengine/lib/yaml/lib/yaml/events.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/loader.py create mode 100644 google_appengine/lib/yaml/lib/yaml/loader.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/nodes.py create mode 100644 google_appengine/lib/yaml/lib/yaml/nodes.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/parser.py create mode 100644 google_appengine/lib/yaml/lib/yaml/parser.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/reader.py create mode 100644 google_appengine/lib/yaml/lib/yaml/reader.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/representer.py create mode 100644 google_appengine/lib/yaml/lib/yaml/representer.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/resolver.py create mode 100644 google_appengine/lib/yaml/lib/yaml/resolver.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/scanner.py create mode 100644 google_appengine/lib/yaml/lib/yaml/scanner.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/serializer.py create mode 100644 google_appengine/lib/yaml/lib/yaml/serializer.pyc create mode 100755 google_appengine/lib/yaml/lib/yaml/tokens.py create mode 100644 google_appengine/lib/yaml/lib/yaml/tokens.pyc create mode 100644 google_appengine/lib/yaml/setup.cfg create mode 100755 google_appengine/lib/yaml/setup.py create mode 100755 google_appengine/lib/yaml/setup_with_libyaml.py create mode 100644 google_appengine/new_project_template/app.yaml create mode 100644 google_appengine/new_project_template/index.yaml create mode 100755 google_appengine/new_project_template/main.py create mode 100755 google_appengine/remote_api_shell.py create mode 100644 google_appengine/templates/logging_console.js create mode 100644 google_appengine/templates/logging_console_footer.html create mode 100644 google_appengine/templates/logging_console_header.html create mode 100644 google_appengine/templates/logging_console_middle.html create mode 100755 google_appengine/tools/bulkload_client.py diff --git a/google_appengine/BUGS b/google_appengine/BUGS new file mode 100644 index 0000000..44f4a4a --- /dev/null +++ b/google_appengine/BUGS @@ -0,0 +1,3 @@ +A list of bugs is available in the Google App Engine SDK project on Google Code. + +The issue tracker is at http://code.google.com/p/googleappengine/issues/. diff --git a/google_appengine/LICENSE b/google_appengine/LICENSE new file mode 100644 index 0000000..53d6fcb --- /dev/null +++ b/google_appengine/LICENSE @@ -0,0 +1,132 @@ +GOOGLE APP ENGINE SDK +===================== +Copyright 2008 Google Inc. +All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + +DJANGO FRAMEWORK +================ +Copyright (c) 2005, the Lawrence Journal-World +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +WebOb +====== + +Copyright (c) 2007 Ian Bicking and Contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +PyYaml +======= +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +cacerts +======= +Version: MPL 1.1/GPL 2.0/LGPL 2.1 + +The contents of this file are subject to the Mozilla Public License Version +1.1 (the "License"); you may not use this file except in compliance with +the License. You may obtain a copy of the License at +http://www.mozilla.org/MPL/ + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +for the specific language governing rights and limitations under the +License. + +The Original Code is the Netscape security libraries. + +The Initial Developer of the Original Code is +Netscape Communications Corporation. +Portions created by the Initial Developer are Copyright (C) 1994-2000 +the Initial Developer. All Rights Reserved. + +Contributor(s): + +Alternatively, the contents of this file may be used under the terms of +either the GNU General Public License Version 2 or later (the "GPL"), or +the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +in which case the provisions of the GPL or the LGPL are applicable instead +of those above. If you wish to allow use of your version of this file only +under the terms of either the GPL or the LGPL, and not to allow others to +use your version of this file under the terms of the MPL, indicate your +decision by deleting the provisions above and replace them with the notice +and other provisions required by the GPL or the LGPL. If you do not delete +the provisions above, a recipient may use your version of this file under +the terms of any one of the MPL, the GPL or the LGPL. + diff --git a/google_appengine/README b/google_appengine/README new file mode 100644 index 0000000..eb0d9db --- /dev/null +++ b/google_appengine/README @@ -0,0 +1,123 @@ +Copyright 2008 Google Inc. +All rights reserved. + +App Engine SDK - Development tools for Google App Engine + +CONTENTS +======== + + * Installing on Mac OSX + * Installing on Windows + * Installing on Linux and other platforms + * Running the SDK + * Using the SDK + * Using the App Engine Launcher + + +INSTALLING ON Mac OSX +===================== +1) Download and install Python 2.5 from http://www.python.org/download/ +2) Download the SDK installer from http://code.google.com/appengine/downloads +3) Install the SDK by double-clicking on the GoogleAppEngine.dmg file and +running the installer. + + +INSTALLING ON WINDOWS +===================== +1) Download and install Python 2.5 from http://www.python.org/download/ +2) Download the SDK installer from http://code.google.com/appengine/downloads +3) Install the SDK by double-clicking on the GoogleAppEngine.msi file and +running the installer. + + +INSTALLING ON LINUX AND OTHER PLATFORMS +=============================== +1) Download and install Python 2.5 from http://www.python.org/download/ +2) Download the SDK zip file from http://code.google.com/appengine/downloads +3) Unpack the zip file. + + +RUNNING THE SDK +========================= +You can run the SDK with the following command: + +dev_appserver.py [options] + +Application root must be the path to the application to run in this server. +Must contain a valid app.yaml or app.yml file. + +Options: + --help, -h View this helpful message. + --debug, -d Use debug logging. (Default false) + --clear_datastore, -c Clear the Datastore on startup. (Default false) + --address=ADDRESS, -a ADDRESS + Address to which this server should bind. (Default + localhost). + --port=PORT, -p PORT Port for the server to run on. (Default 8080) + --blobstore_path=PATH Path to use for storing Blobstore file stub data. + --datastore_path=PATH Path to use for storing Datastore file stub data. + (Default /tmp/dev_appserver.datastore) + --use_sqlite Use the new, SQLite based datastore stub. + (Default false) + --history_path=PATH Path to use for storing Datastore history. + (Default /tmp/dev_appserver.datastore.history) + --require_indexes Disallows queries that require composite indexes + not defined in index.yaml. + --smtp_host=HOSTNAME SMTP host to send test mail to. Leaving this + unset will disable SMTP mail sending. + (Default '') + --smtp_port=PORT SMTP port to send test mail to. + (Default 25) + --smtp_user=USER SMTP user to connect as. Stub will only attempt + to login if this field is non-empty. + (Default ''). + --smtp_password=PASSWORD Password for SMTP server. + (Default '') + --enable_sendmail Enable sendmail when SMTP not configured. + (Default false) + --show_mail_body Log the body of emails in mail stub. + (Default false) + --auth_domain Authorization domain that this app runs in. + (Default gmail.com) + --debug_imports Enables debug logging for module imports, showing + search paths used for finding modules and any + errors encountered during the import process. + --allow_skipped_files Allow access to files matched by app.yaml's + skipped_files (default False) + --disable_static_caching Never allow the browser to cache static files. + (Default enable if expiration set in app.yaml) + --disable_task_running When supplied, tasks will not be automatically + run after submission and must be run manually + in the local admin console. + --task_retry_seconds How long to wait in seconds before retrying a + task after it fails during execution. + (Default '30') + + + +USING THE SDK +======================= +For instructions on getting started with Google App Engine, please see the +Google App Engine Getting Started Guide + +http://code.google.com/appengine/docs/gettingstarted + + +USING THE APP ENGINE LAUNCHER +============================= +The Windows and Mac OSX Python SDKs include an additional development tool +called the App Engine Launcher. This tool provides a simple graphical +interface to create projects, run them locally, and deploy them to Google's App +Engine servers. It can be used in place of the dev_appserver and appcfg +command-line tools. + +The Windows SDK can optionally install a desktop short-cut during +installation. If you are missing the short-cut, you can find the launcher in +the launcher subdirectory of your App Engine installation. The default +location is +C:\Program Files\Google\google_appengine\launcher\GoogleAppEngineLauncher.exe + +In Mac OSX, the Launcher is installed by dragging it out of the .dmg to a +location specified by the user. The Launcher contains the SDK inside of it. +A typical drag-install destination for the Launcher and SDK is +/Applications/GoogleAppEngineLauncher.app diff --git a/google_appengine/RELEASE_NOTES b/google_appengine/RELEASE_NOTES new file mode 100644 index 0000000..a0780c8 --- /dev/null +++ b/google_appengine/RELEASE_NOTES @@ -0,0 +1,611 @@ +Copyright 2008 Google Inc. +All rights reserved. + +App Engine Python SDK - Release Notes + +Version 1.3.6 +================================= +- Multitenancy is now supported in the datastore, allowing better + compartmentalization of user data. +- Automatic image thumbnailing is now available in the Images API using + get_serving_url(). +- Users can now serve custom static error pages for over_quota, dos_api_denial + and default cases. +- Results of datastore count() queries and offsets for all datastore queries + are no longer capped at 1000. +- Added a pause queue button to the task queue details page in the Admin + Console. +- Historical graphs have been added to all of the dashboard graphs in the Admin + Console. +- Content-range headers are supported on Blobstore downloads. +- Remote API now supports the Blobstore API. +- New method to allocate datastore ids in a given range: db.allocate_id_range(). +- New db method is_in_transaction() determines if a transaction is still open. +- Increased several rate limited quotas for free applications. +- Fixed an issue in db.py where unindexed property lists for user-specified + property names were ignored. +- Fixed an issue where the task queue uses local time instead of UTC time to + compute job ETA. + http://code.google.com/p/googleappengine/issues/detail?id=2508 +- Fixed an issue in the SDK with datastore cursors being too large. + http://code.google.com/p/googleappengine/issues/detail?id=3152 + +Version 1.3.5 +================================= +- Developers can enable Python pre-compilation to decrease start up time for + new application instances. This can be enabled by including the + derived_file_type in your app.yaml. +- Ability to configure the Task Queue storage limit with the + total_storage_limit field in the queue.yaml file. +- Task Queues now support up to 50 qps per queue, up from 50 qps per app. +- Developers can programmatically access Blobs with BlobReader, a file-like + interface for reading blobs. +- Bulkloader transform helpers for lists and hierarchical keys were added. +- remote_api_shell commands can be sent over HTTPS or HTTP. +- Admin Console logs now include information on request time latency. +- Db.delete will now accept an iterable, in addition to a list of models or + keys. +- The datastore now supports end cursors. +- Fixed an issue properly handling a query with an offset that returns no + results. +- Fixed an issue that improperly allowed quad-dotted netmasks as subnet prefixes + for the DoS API. +- Fixed an issue via user submitted patch in the SDK dataviewer displaying + multiline StringProperties. + http://code.google.com/p/googleappengine/issues/detail?id=502 + +Version 1.3.4 +================================= +- New bulkloader configuration syntax and wizard for easier import/export with + the datastore. +- Applications can now be configured to authenticate with OpenID by selecting + the OpenID option when creating your application in the admin console. + http://code.google.com/p/googleappengine/issues/detail?id=248 + http://code.google.com/p/googleappengine/issues/detail?id=56 +- New API to allow App Engine apps to act as OAuth service providers. + http://code.google.com/p/googleappengine/issues/detail?id=919 +- Auto task execution is now enabled in the dev_appserver. To turn this off + use the flag --disable_task_running. +- Fixed an issue using db.put() with constructor initialized id based keys. + http://code.google.com/p/googleappengine/issues/detail?id=3209 + +Version 1.3.3 +================================= +- A new experimental feature allows you to set dev_appserver datastore file + stub to use sqlite. To enable, set the flag --use_sqlite=true. +- It is now possible to implement properties on db.Expando. +- Fixed a datastore issue where an error was thrown when setting a query offset + to more than the number of results throws an error. + http://code.google.com/p/googleappengine/issues/detail?id=2875 +- Fixed issue not allowing ByteString type to be viewed in the Development + Console datastore viewer. + http://code.google.com/p/googleappengine/issues/detail?id=1176 + +Version 1.3.2 +================================= +- New API to read the contents of uploaded Blobs (fetch_data) + http://code.google.com/p/googleappengine/issues/detail?id=2536 +- URLFetch now supports accessing ports 80-90, 440-450, and 1024-65535 +- Mail API now allows common document formats as attachments + http://code.google.com/p/googleappengine/issues/detail?id=494 +- The Task Queue API now supports adding multiple tasks in a single call to + Queue.add() +- Fixed charset handling for inbound emails + http://code.google.com/p/googleappengine/issues/detail?id=2326 +- Fixed issue with compositing background colors in dev_appserver +- New feature in the datastore to specify whether to use strong or eventually + consistent reads (the default is strong) +- New datastore feature allows setting deadlines for operations +- Increased the maximum Task Queue refill rate from 20/s to 50/s +- Support for IP blacklisting to prevent denial of service (DoS) attacks +- Fix an issue with Mac Launcher in Mac OSX 10.5.5 + http://code.google.com/p/googleappengine/issues/detail?id=778 +- Fix issue with slow updates when there are many skipped files + http://code.google.com/p/googleappengine/issues/detail?id=2492 +- Fix issue with cursor not updating when using a GqlQuery + http://code.google.com/p/googleappengine/issues/detail?id=2757 + +Version 1.3.1 +================================ + - Datastore Query Cursors + http://code.google.com/appengine/docs/python/datastore/queriesandindexes.html#Query_Cursors + - Transactional Task Creation + - Support for Custom Admin Console pages + - New "month" and "synchronized" syntax for Cron configuration + http://code.google.com/appengine/docs/java/config/cron.html + - Application Stats library now included in with SDK + http://code.google.com/appengine/docs/python/tools/appstats.html + - Bulk Loader supports bulk downloading all kinds simultaneously + - appcfg.py validates SSL certificates for HTTPS on Upload + - Support for ETags, If-matches, If-not-matches HTTP Headers, as well as 304 + status codes now available on static files (not available on the + dev_appserver or Blobstore blobs) + http://code.google.com/p/googleappengine/issues/detail?id=575 + +Version 1.3.0 - December 14, 2009 +================================ + - Adds support for the new Blobstore API + +Version 1.2.8 - October 28, 2009 +================================ + - New memcache offset_multi method and batch support in incr and decr. + - Urlfetch Response object now contains final_url of 302 redirects. + http://code.google.com/p/googleappengine/issues/detail?id=1464 + - Additional file extensions permitted when sending mail. + http://code.google.com/p/googleappengine/issues/detail?id=494 + - Fixed issue decoding messages for incoming mail. + http://code.google.com/p/googleappengine/issues/detail?id=2289 + - Fixed issue with datastore list properties containing both blob (or text) + and non-blob values. + - Admin console includes more information about indexes being built. + - Fixed Users API usage with remote_api + http://code.google.com/p/googleappengine/issues/detail?id=1205 + - Fixed issue with IN queries in remote_api + http://code.google.com/p/googleappengine/issues/detail?id=1986 + - Bulk Loader --dump and --restore now work across app IDs. + - Bulk Loader --restore works with numeric IDs. + - Bulk Loader exporter maps __key__ property to the entity key. + - Fixed issue in Bulk Loader with missing properties on export. + http://code.google.com/p/googleappengine/issues/detail?id=2068 + - Fixed issue in Bulk Loader with line breaks in data. + - Fixed exception in Bulk Loader with certain data ranges. + http://code.google.com/p/googleappengine/issues/detail?id=2085 + - Added SERVER_SOFTWARE environment variable to runtime. + - Over Quota HTTP status code changed from 403 to 503 + http://code.google.com/p/googleappengine/issues/detail?id=961 + - Task Queue now considers all HTTP 2xx status codes to represent success + http://code.google.com/p/googleappengine/issues/detail?id=1779 + - Task Queue now supports purging all tasks in a queue from the Admin Console. + http://code.google.com/p/googleappengine/issues/detail?id=2159 + - Task Queue now supports deleting a non-empty queue from the Admin Console. + http://code.google.com/p/googleappengine/issues/detail?id=1740 + - New "auth_fail_action: unauthorized" option in app.yaml: when present, a + 401 status code will be returned instead of a 302 redirect to the Google + Accounts login page for pages with login: required. [Python only for now] + + +Version 1.2.7 - October 14, 2009 +================================ + - Changed the 'key' parameter to Model.__init__ to be keyword only. + - Fixed taskqueue import in Remote API. + http://code.google.com/p/googleappengine/issues/detail?id=2259 + + +Version 1.2.6 - September 17, 2009 +================================== + - Added incoming email support. + http://code.google.com/p/googleappengine/issues/detail?id=165 + - Remote API now supports XMPP and task queues. + - The default for all handlers is now secure: optional. Users can + now access all pages via SSL unless explicitly disallowed. + - Remote API now supports HTTPS. + http://code.google.com/p/googleappengine/issues/detail?id=1461 + - Appcfg now uses https by default. + http://code.google.com/p/googleappengine/issues/detail?id=794 + - Appcfg.py now supports the --application and --version flags to + override the values specified in app.yaml. + http://code.google.com/p/googleappengine/issues/detail?id=670 + - GQL now supports '= NULL' queries. + - The db.Model constructor now supports explicitly setting a key + (and thus an id) for a Model instance. + - New Datastore stats api. Stats are also visible in the admin console. + - Bulkloader dump and restore now supports restoring to a different + app id and restoring numeric keys. + + +Version 1.2.5 - August 13, 2009 +=============================== + - The Windows Python SDK now includes a GUI launcher, similar to the Mac SDK. + - Added XMPP support. + http://code.google.com/appengine/docs/python/xmpp + http://code.google.com/p/googleappengine/issues/detail?id=231 + - Datastore now supports multiple writes to the same entity within a + transaction. + - Datastore entity key names can now start with a digit. + http://code.google.com/p/googleappengine/issues/detail?id=1352 + - Datastore now supports ancestor + kind queries without a composite index + http://code.google.com/p/googleappengine/issues/detail?id=1003 + - Bulkloader now supports configurationless dump and restore with new + --dump and --restore options. + - Bulkloader now supports a --dry_run flag to testing data prior to uploading. + - Appcfg.py now allows specifying any end date for request_logs. + - Urlfetch now allows setting the Referer header. + http://code.google.com/p/googleappengine/issues/detail?id=445 + - Urlfetch stub now correctly handles HEAD requests. + http://code.google.com/p/googleappengine/issues/detail?id=866 + - New remote_api_shell tool for interactive remote_api operations. + - New google.ext.ereporter module to collect and email exception reports. + - New google.ext.deferred module to execute ad-hoc tasks on the Task Queue. + +Version 1.2.4 - July 16, 2009 +============================= + - Added support for kindless queries, ie. transaction descendant queries. + http://code.google.com/p/googleappengine/issues/detail?id=913 + - Composite indexes no longer required for certain types of key queries. + - Improved exception reporting in the bulkloader. + - Datastore transaction RPC sent at beginning of transaction rather than + upon first Datastore request. + - PolyModel supports keys_only query. + http://code.google.com/p/googleappengine/issues/detail?id=1630 + - Remote API supports more API's (Images, Memcache and URLFetch). + http://code.google.com/p/googleappengine/issues/detail?id=1596 + - Remote API shell. + - Support for multiple inheritance for Model and PolyModel. + - Enhancement to SearchableModel allowing multiple properties to be + indexed. + - Various code quality improvements. + +Version 1.2.3 - June 1, 2009 +============================ + + - Task Queue support available as google.appengine.api.labs.taskqueue. + http://code.google.com/appengine/docs/python/taskqueue + - Django 1.0 support. You must install Django locally on your machine + for the SDK but no longer need to upload it to App Engine. + from google.appengine.dist import use_library + use_library('django', '1.0') + http://code.google.com/p/googleappengine/issues/detail?id=872 + - Urlfetch supports asynchronous requests. + http://code.google.com/p/googleappengine/issues/detail?id=958 + - Urlfetch in SDK now matches App Engine more closely: + By default, it now sets the referer header, does not set the Accept + header, and sets Accept-Encoding to gzip. + http://code.google.com/p/googleappengine/issues/detail?id=970 + - Fixed issue with httplib and absolute URLs. + http://code.google.com/p/googleappengine/issues/detail?id=1311 + - Memcache key length is no longer restricted to 250 bytes: longer keys + will be replaced with a hash of the key. + - Datastore ancestor queries now work within transactions. + - Datastore transactions in SDK now snapshot on the first operation so they + do not see writes made during the transaction. Matches App Engine. + +Version 1.2.2 - April 22, 2009 +============================== + + - New quota API which returns the CPU usage of the current request. + from google.appengine.api import quota + cpu_usage_so_far = quota.get_request_cpu_usage() + - Urlfetch fetch now has support for user configurable deadlines. + http://code.google.com/p/googleappengine/issues/detail?id=79 + - Urlfetch in the SDK allows the Accept-Encoding header to match App Engine. + http://code.google.com/p/googleappengine/issues/detail?id=1071 + - urllib now supports HTTPS in addition to HTTP + http://code.google.com/p/googleappengine/issues/detail?id=1156 + - Datastore indexes on single properties can now be disabled by setting + indexed=False on the property constructor. + - Datastore now supports Key-only queries, using either SELECT __key__ or + or db.Query(Model, keys_only=True) + - Fixed issues with Datastore IN filters and sorting: sort order is now + correct, and can be used with __key__. + http://code.google.com/p/googleappengine/issues/detail?id=1100 + http://code.google.com/p/googleappengine/issues/detail?id=1016 + - Cron supports additional time specification formats. + http://code.google.com/p/googleappengine/issues/detail?id=1261 + - Fixed an issue in the dev_appserver admin console datastore viewer + (/_ah/admin/datastore) with sorting columns containing None types. + http://code.google.com/p/googleappengine/issues/detail?id=1007 + - Bulk Loader improvements: New appcfg download_data command. + Better backoff support and debugging output for long requests. + - New --vhost flag on appcfg.py request_logs command to select logs for + a particular host. + - Python _ast module is now available for import + http://code.google.com/p/googleappengine/issues/detail?id=779 + - Fixed issue with the color argument of the Images API composite method. + +Version 1.2.1 - April 13, 2009 +============================= + + - Stable, unique IDs for User objects. The Users service now + provides a unique user_id for each user that stays the same even + if a user changes her email address. + http://code.google.com/p/googleappengine/issues/detail?id=1019 + - The Images API now supports compositing images and calculating + a color histogram for an image. + - New allowed mail attachment types: ics, vcf + http://code.google.com/p/googleappengine/issues/detail?id=494 + - Urlfetch requests can now set the User-Agent header. + http://code.google.com/p/googleappengine/issues/detail?id=342 + - An App Engine-specific version of the Python PyCrypto cryptography + library is now available. Learn more at + http://code.google.com/appengine/docs/python/tools/libraries.html + - The bulk loader configuration format has changed.to allow non-CSV + input. This change is not backwards compatible, so you will need to + update your code. + An early release of the bulk downloader is also now available in + bulkloader.py. Learn more about these changes at: + http://code.google.com/appengine/docs/python/tools/uploadingdata.html + - Fixed parsing of unicode GQL queries. + http://code.google.com/p/googleappengine/issues/detail?id=1105 + - Fixed dev_appserver security restrictions for os.path + http://code.google.com/p/googleappengine/issues/detail?id=1068 + - Fixed Reply-To header set in emails sent from dev_appserver. + http://code.google.com/p/googleappengine/issues/detail?id=1017 + + +Version 1.2.0 - March 24, 2009 +============================== + - Cron support. Appcfg.py will upload the schedule to App Engine. + The dev_appserver console at /_ah/admin describes your schedule but does + not automatically run scheduled jobs. Learn more at + http://code.google.com/appengine/docs/python/config/cron.html + - New allow_skipped_files flag in dev_appserver to allow it to read files + which are not available in App Engine. + http://code.google.com/p/googleappengine/issues/detail?id=550 + - New upload_data command in appcfg to run the bulk uploader. + http://code.google.com/appengine/docs/python/tools/uploadingdata.html + +Version 1.1.9 - February 2, 2009 +================================ + + - HTTP Request and Response limit raised to 10MB from 1MB. + Note that API call limits remain at 1MB. + http://code.google.com/p/googleappengine/issues/detail?id=78 + - urllib and urllib2 now available, implemented using urlfetch. + Also adds additional stubs which may enable other modules. + http://code.google.com/p/googleappengine/issues/detail?id=61 + http://code.google.com/p/googleappengine/issues/detail?id=68 + http://code.google.com/p/googleappengine/issues/detail?id=572 + http://code.google.com/p/googleappengine/issues/detail?id=821 + - Early release of a new data bulk upload tool, bulkloader.py + http://code.google.com/appengine/docs/python/tools/uploadingdata.html + - New remote_api for datastore at google.appengine.ext.remote_api + - Single property descending indexes are automatically generated. + - Added db.Query support for IN and != operators. + http://code.google.com/p/googleappengine/issues/detail?id=751 + - Fixed issue where gql date/time parsing could not handle Unicode strings. + - Fixed issue with db model instance key() returning the wrong key for + unsaved instances with parent as key + http://code.google.com/p/googleappengine/issues/detail?id=883 + - New run_in_transaction_custom_retries method for datastore. + - Fixed issue with relative dev_appserver datastore and history paths. + http://code.google.com/p/googleappengine/issues/detail?id=845 + - Static files and skipped files are not readable in dev_appserver, to match + the behavior on App Engine. + http://code.google.com/p/googleappengine/issues/detail?id=550 + - Images API allows multiple transforms of the same type in one request. A + limit of 10 total transforms per request has been added. + - PIL import will work with both PIL.Image and Image. + http://code.google.com/p/googleappengine/issues/detail?id=929 + - Fixed an issue with sending email in dev_appserver when the application + code changed. + http://code.google.com/p/googleappengine/issues/detail?id=182 + - Memcache counters (incr/decr) do nothing on non positive integers to match + the behavior on App Engine. + http://code.google.com/p/googleappengine/issues/detail?id=918 + +Version 1.1.8 - January 7, 2008 +================================= + - Skip_files RegexStr validator allows lists to for regex-ors. + http://code.google.com/p/googleappengine/issues/detail?id=81 + - sys.path and sys.argv are no longer reset for each request. + http://code.google.com/p/googleappengine/issues/detail?id=772 + - New ByteString data type for the datastore. Indexed non-text short-blob. + - UserProperty now takes auto_current_user and auto_current_user_add + attributes. + - Support for polymorphic models and queries. + - db.Model.order() now supports __key__. + http://code.google.com/p/googleappengine/issues/detail?id=884 + - Urlfetch no longer sets content-length: 0 when there is no body. + http://code.google.com/p/googleappengine/issues/detail?id=817 + - Get height and width of an image via the Images API. + http://code.google.com/p/googleappengine/issues/detail?id=435 + - Limit auto-Bcc of email sender to the case where the email sender is the + currently-logged-in user. + - Adds limit of 100 order/filters on datastore query size to the SDK. + - Fix unicode support for the bulkloader + http://code.google.com/p/googleappengine/issues/detail?id=157 + - Bulkload.py from the appengine/tools directory to the appengine/ directory + - Modify webapp to use logging.exception instead of logging.error. + - Additional fixes to SDK sanitizing response headers to match production. + http://code.google.com/p/googleappengine/issues/detail?id=198 + +Version 1.1.7 - November 20, 2008 +================================= + - Fixed an issue with urlfetch response headers. + http://code.google.com/p/googleappengine/issues/detail?id=877 + +Version 1.1.6 - November 17, 2008 +================================= + + - Datastore now supports filtering and sorting on the __key__ special + property, which evaluates to each entity's key. + - Fixed a bug where it was possible to append None to ListProperty. + - Datastore appengine.ext.db models allow deletion by key without + instantiating a model instance. + - Datastore models allow access to key name before put() if key_name given. + - Datastore fetch max results and max query offset match production limits. + - Fixed an issue in production where query fails with NeedIndexError when + a model has two ancestor indexes. + http://code.google.com/p/googleappengine/issues/detail?id=423 + - Allow trailing whitespace in PropertyValueFromString for datetime. + - Fixed to_xml on models with binary data in a BlobProperty: they now + are base64 encoded. + Note: This changes XML serialization. + http://code.google.com/p/googleappengine/issues/detail?id=430 + - Fixed an issue with setting expando attributes. + http://code.google.com/p/googleappengine/issues/detail?id=431 + - Fixed an issue where TypeError was raised instead of NeedIndexError for + "merge join" queries, i.e. queries with only equals filters and no ancestor + or sort orders, that still need an index. + http://code.google.com/p/googleappengine/issues/detail?id=749 + - URLFetch in the SDK now has the same 5 second timeout to match production. + - URLFetch response headers are combined + http://code.google.com/p/googleappengine/issues/detail?id=412 + - URLFetch now uses original method when following a redirect. + http://code.google.com/p/googleappengine/issues/detail?id=363 + - URLFetch logs a warning when using a non standard port. + http://code.google.com/p/googleappengine/issues/detail?id=436 + - URLFetch allows integers as values in request headers. + - Enforce response size and API request size limits to match production. + http://code.google.com/p/googleappengine/issues/detail?id=447 + - SDK sanitizes response headers to match production + http://code.google.com/p/googleappengine/issues/detail?id=198 + - Login URLs now require login in the SDK to match production. + http://code.google.com/p/googleappengine/issues/detail?id=53 + - Fixed an issue with long URLs in HTTP 302 redirect responses. + http://code.google.com/p/googleappengine/issues/detail?id=407 + - Fixed an issue with regular expressions in static_files in app.yaml + http://code.google.com/p/googleappengine/issues/detail?id=711 + - SDK only allows "C" locale to match production. + http://code.google.com/p/googleappengine/issues/detail?id=356 + - Support the bufsize positional arg in open()/file(). + - lstat is aliased to stat. + - appcfg handles index building errors more gracefully. + - Fixed an issue with symlinks in the path to the Python core libraries. + + +Version 1.1.5 - September 29, 2008 +================================== + + - Additional fixes for file paths on Windows and OSX. + - Sped up the datastore stub. + - Allow different types in list properties in datastore.Entity and Expando. + - Add add_multi and replace_multi to memcache API. + http://code.google.com/appengine/docs/memcache/clientclass.html#Client_add_multi + http://code.google.com/appengine/docs/memcache/clientclass.html#Client_replace_multi + - Ignore errors from the API proxy when calling memcache read methods. + - Set the webapp Request charset property more accurately from CONTENT_TYPE. + - Fixed an issue in the development console with schema caching. + - Fixed an issue with StringListProperty not returning a class + http://code.google.com/p/googleappengine/issues/detail?id=415 + - Fixed an issue in the development console where quotes couldn't be used + within fields. + - Fixed an issue with TimeProperty("0:0") (midnight). + http://code.google.com/p/googleappengine/issues/detail?id=279 + +Version 1.1.4 - September 26, 2008 +================================== + + - Fixed issue with incorrectly escaping static_files paths on Windows. + - Workaround -inf not being supported on Windows in Datastore. + +Version 1.1.3 - September 8, 2008 +================================= + + - Added support for zipimport. + http://code.google.com/p/googleappengine/issues/detail?id=70 + http://code.google.com/p/googleappengine/issues/detail?id=161 + - Added zipserve module for serving static content from a zip file. + See google/appengine/ext/zipserve/__init__.py for more information. + - Added a memcache viewer to the development console. + http://code.google.com/appengine/docs/thedevwebserver.html#The_Development_Console + - Added new follow_redirects flag to the URLFetch service. + http://code.google.com/p/googleappengine/issues/detail?id=404 + - Fixed caching headers for static content. + - Fixed an issue with incorrectly escaping paths on Windows. + - Fixed an issue with the current directory while running applications. + +Version 1.1.2 - August 20, 2008 +=============================== + + - Batch puts across Datastore entity groups. + - Transaction retries reduced from 10 to 3. + - Fixed certain transaction failures being silent. + - Added support for indexes with a single repeated property. + +Version 1.1.1 - July 21, 2008 +============================= + + - Fixed DELETE for URLFetch on dev_appserver. + http://code.google.com/p/googleappengine/issues/detail?id=566 + - Fixed PATH_INFO to be un-escaped version of the path. + http://code.google.com/p/googleappengine/issues/detail?id=267 + http://code.google.com/p/googleappengine/issues/detail?id=457 + - Fixed order function testing for property on Expando class. + - Support all mail attachment mime-types under Windows. + - Added support for date and time objects to GQL. + http://code.google.com/p/googleappengine/issues/detail?id=318 + - Fixed memcache KeyError problem. + http://code.google.com/p/googleappengine/issues/detail?id=417 + - Default URLFetch POST content-type is x-www-form-urlencoded. + - Fixed problems where global variables would be set to None + when a request raised an exception or returned an error + response. + - Added support for GIFs and JPEG using PIL. + - Added support for type conversion of literals to GQL. + - Added support for pickling Expando instances. + http://code.google.com/p/googleappengine/issues/detail?id=545 + - Added APPLICATION_ID environment variable to runtime. + - Added support for key_name to djangoforms. + - Added ability to put multiple transaction groups in one request + outside of transactions. + - Added support for downloading request logs using appcfg. + http://code.google.com/p/googleappengine/issues/detail?id=76 + - Fixed DateProperty not supporting values before 1970 and beyond + Jan. 19, 2038. + http://code.google.com/p/googleappengine/issues/detail?id=352 + - Set cap of 5000 indexed properties per entity. + - GoogleAppEngineLauncher now has context menus in the main project + window. + - UI improvements to GoogleAppEngineLauncher preferences window. + - Fixed GoogleAppEngineLauncher broken symlink for bulk_uploadclient. + +Version 1.1.0 - May 28, 2008 +============================ + + - Added an API for image manipulation. + http://code.google.com/p/googleappengine/issues/detail?id=38 + - Added memcache API. + - Fixed URLFetch for URLs with query strings. + http://code.google.com/p/googleappengine/issues/detail?id=341 + http://code.google.com/p/googleappengine/issues/detail?id=346 + http://code.google.com/p/googleappengine/issues/detail?id=369 + - Added support for multiple values for the same filter string. + - Fixed URLFetch's referrer to now set itself to the application's + host-name. + - Added --show_mail_body flag to dev_appserver.py. + - Added support for IN and != to GQL. + - Fixed URLFetch to accept strings as well as constant integers. + http://code.google.com/p/googleappengine/issues/detail?id=234 + - Added CURRENT_VERSION_ID environment variable. + - Fixed uploading issues affecting @googlemail.com developers. + http://code.google.com/p/googleappengine/issues/detail?id=119 + - Fixed Datastore API to allow the assignment of [] to non-dynamic + DB attributes. + http://code.google.com/p/googleappengine/issues/detail?id=276 + http://code.google.com/p/googleappengine/issues/detail?id=254 + - Fixed NeedIndexError to include the index that the query needed. + +Version 1.0.2 - May 15, 2008 +============================ + + - Fixed UTC timezone issue on Windows. + http://code.google.com/p/googleappengine/issues/detail?id=131 + - Fixed webapp template cache bug. + http://code.google.com/p/googleappengine/issues/detail?id=273 + - URLFetch service redirect behavior now matches deployed behavior. + http://code.google.com/p/googleappengine/issues/detail?id=84 + - Better handling of bad HOMEDRIVE parameters on Windows. + http://code.google.com/p/googleappengine/issues/detail?id=27 + - Fixed HTTP response header termination. + http://code.google.com/p/googleappengine/issues/detail?id=209 + - Fixed behavior with source files that have Windows line-endings or + missing line-endings. + http://code.google.com/p/googleappengine/issues/detail?id=237 + http://code.google.com/p/googleappengine/issues/detail?id=258 + - Fixed C-Extension module loading issues. + http://code.google.com/p/googleappengine/issues/detail?id=95 + http://code.google.com/p/googleappengine/issues/detail?id=83 + - Fixed Windows DLL extension loading issues. + http://code.google.com/p/googleappengine/issues/detail?id=222 + - Added missing os.uname function. + http://code.google.com/p/googleappengine/issues/detail?id=186 + - Windows installer can now over-install. + http://code.google.com/p/googleappengine/issues/detail?id=241 + - Windows installer now allows installation even if it can't find Python. + http://code.google.com/p/googleappengine/issues/detail?id=5 + - Fixed skip_files exception. + http://code.google.com/p/googleappengine/issues/detail?id=80 + - Better error handling for cookie-file related problems. + - User platform, SDK version, and Python version are now supplied to + server-side on deployment; also supplied on dev_appserver start-up + if the "nag" is enabled. + + +Version 1.0.1 - April 14, 2008 +============================== + + - Fixed app.yaml static_dir attribute on Windows. + - Fixed uploading large files on OSX. + - Fixed recursion issue in webapp template rendering cache. + - Fixed MacPorts installation. diff --git a/google_appengine/VERSION b/google_appengine/VERSION new file mode 100644 index 0000000..cb60ed2 --- /dev/null +++ b/google_appengine/VERSION @@ -0,0 +1,3 @@ +release: "1.3.6" +timestamp: 1278528410 +api_versions: ['1'] diff --git a/google_appengine/appcfg.py b/google_appengine/appcfg.py new file mode 100755 index 0000000..61cb609 --- /dev/null +++ b/google_appengine/appcfg.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Convenience wrapper for starting an appengine tool.""" + + +import os +import sys + +if not hasattr(sys, 'version_info'): + sys.stderr.write('Very old versions of Python are not supported. Please ' + 'use version 2.5 or greater.\n') + sys.exit(1) +version_tuple = tuple(sys.version_info[:2]) +if version_tuple < (2, 4): + sys.stderr.write('Error: Python %d.%d is not supported. Please use ' + 'version 2.5 or greater.\n' % version_tuple) + sys.exit(1) +if version_tuple == (2, 4): + sys.stderr.write('Warning: Python 2.4 is not supported; this program may ' + 'break. Please use version 2.5 or greater.\n') + +DIR_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +SCRIPT_DIR = os.path.join(DIR_PATH, 'google', 'appengine', 'tools') + +EXTRA_PATHS = [ + DIR_PATH, + os.path.join(DIR_PATH, 'lib', 'antlr3'), + os.path.join(DIR_PATH, 'lib', 'django'), + os.path.join(DIR_PATH, 'lib', 'fancy_urllib'), + os.path.join(DIR_PATH, 'lib', 'ipaddr'), + os.path.join(DIR_PATH, 'lib', 'webob'), + os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'), +] + +SCRIPT_EXCEPTIONS = { + "dev_appserver.py" : "dev_appserver_main.py" +} + + +def fix_sys_path(): + """Fix the sys.path to include our extra paths.""" + sys.path = EXTRA_PATHS + sys.path + + +def run_file(file_path, globals_, script_dir=SCRIPT_DIR): + """Execute the file at the specified path with the passed-in globals.""" + fix_sys_path() + script_name = os.path.basename(file_path) + script_name = SCRIPT_EXCEPTIONS.get(script_name, script_name) + script_path = os.path.join(script_dir, script_name) + execfile(script_path, globals_) + + +if __name__ == '__main__': + run_file(__file__, globals()) diff --git a/google_appengine/bulkload_client.py b/google_appengine/bulkload_client.py new file mode 100755 index 0000000..61cb609 --- /dev/null +++ b/google_appengine/bulkload_client.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Convenience wrapper for starting an appengine tool.""" + + +import os +import sys + +if not hasattr(sys, 'version_info'): + sys.stderr.write('Very old versions of Python are not supported. Please ' + 'use version 2.5 or greater.\n') + sys.exit(1) +version_tuple = tuple(sys.version_info[:2]) +if version_tuple < (2, 4): + sys.stderr.write('Error: Python %d.%d is not supported. Please use ' + 'version 2.5 or greater.\n' % version_tuple) + sys.exit(1) +if version_tuple == (2, 4): + sys.stderr.write('Warning: Python 2.4 is not supported; this program may ' + 'break. Please use version 2.5 or greater.\n') + +DIR_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +SCRIPT_DIR = os.path.join(DIR_PATH, 'google', 'appengine', 'tools') + +EXTRA_PATHS = [ + DIR_PATH, + os.path.join(DIR_PATH, 'lib', 'antlr3'), + os.path.join(DIR_PATH, 'lib', 'django'), + os.path.join(DIR_PATH, 'lib', 'fancy_urllib'), + os.path.join(DIR_PATH, 'lib', 'ipaddr'), + os.path.join(DIR_PATH, 'lib', 'webob'), + os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'), +] + +SCRIPT_EXCEPTIONS = { + "dev_appserver.py" : "dev_appserver_main.py" +} + + +def fix_sys_path(): + """Fix the sys.path to include our extra paths.""" + sys.path = EXTRA_PATHS + sys.path + + +def run_file(file_path, globals_, script_dir=SCRIPT_DIR): + """Execute the file at the specified path with the passed-in globals.""" + fix_sys_path() + script_name = os.path.basename(file_path) + script_name = SCRIPT_EXCEPTIONS.get(script_name, script_name) + script_path = os.path.join(script_dir, script_name) + execfile(script_path, globals_) + + +if __name__ == '__main__': + run_file(__file__, globals()) diff --git a/google_appengine/bulkloader.py b/google_appengine/bulkloader.py new file mode 100755 index 0000000..61cb609 --- /dev/null +++ b/google_appengine/bulkloader.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Convenience wrapper for starting an appengine tool.""" + + +import os +import sys + +if not hasattr(sys, 'version_info'): + sys.stderr.write('Very old versions of Python are not supported. Please ' + 'use version 2.5 or greater.\n') + sys.exit(1) +version_tuple = tuple(sys.version_info[:2]) +if version_tuple < (2, 4): + sys.stderr.write('Error: Python %d.%d is not supported. Please use ' + 'version 2.5 or greater.\n' % version_tuple) + sys.exit(1) +if version_tuple == (2, 4): + sys.stderr.write('Warning: Python 2.4 is not supported; this program may ' + 'break. Please use version 2.5 or greater.\n') + +DIR_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +SCRIPT_DIR = os.path.join(DIR_PATH, 'google', 'appengine', 'tools') + +EXTRA_PATHS = [ + DIR_PATH, + os.path.join(DIR_PATH, 'lib', 'antlr3'), + os.path.join(DIR_PATH, 'lib', 'django'), + os.path.join(DIR_PATH, 'lib', 'fancy_urllib'), + os.path.join(DIR_PATH, 'lib', 'ipaddr'), + os.path.join(DIR_PATH, 'lib', 'webob'), + os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'), +] + +SCRIPT_EXCEPTIONS = { + "dev_appserver.py" : "dev_appserver_main.py" +} + + +def fix_sys_path(): + """Fix the sys.path to include our extra paths.""" + sys.path = EXTRA_PATHS + sys.path + + +def run_file(file_path, globals_, script_dir=SCRIPT_DIR): + """Execute the file at the specified path with the passed-in globals.""" + fix_sys_path() + script_name = os.path.basename(file_path) + script_name = SCRIPT_EXCEPTIONS.get(script_name, script_name) + script_path = os.path.join(script_dir, script_name) + execfile(script_path, globals_) + + +if __name__ == '__main__': + run_file(__file__, globals()) diff --git a/google_appengine/demos/guestbook/app.yaml b/google_appengine/demos/guestbook/app.yaml new file mode 100644 index 0000000..18b446e --- /dev/null +++ b/google_appengine/demos/guestbook/app.yaml @@ -0,0 +1,8 @@ +application: guestbook +version: 1 +runtime: python +api_version: 1 + +handlers: +- url: .* + script: guestbook.py diff --git a/google_appengine/demos/guestbook/guestbook.py b/google_appengine/demos/guestbook/guestbook.py new file mode 100755 index 0000000..f942dca --- /dev/null +++ b/google_appengine/demos/guestbook/guestbook.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import cgi +import datetime +import wsgiref.handlers + +from google.appengine.ext import db +from google.appengine.api import users +from google.appengine.ext import webapp + +class Greeting(db.Model): + author = db.UserProperty() + content = db.StringProperty(multiline=True) + date = db.DateTimeProperty(auto_now_add=True) + + +class MainPage(webapp.RequestHandler): + def get(self): + self.response.out.write('') + + greetings = db.GqlQuery("SELECT * " + "FROM Greeting " + "ORDER BY date DESC LIMIT 10") + + for greeting in greetings: + if greeting.author: + self.response.out.write('%s wrote:' % greeting.author.nickname()) + else: + self.response.out.write('An anonymous person wrote:') + self.response.out.write('
%s
' % + cgi.escape(greeting.content)) + + self.response.out.write(""" +
+
+
+
+ + """) + + +class Guestbook(webapp.RequestHandler): + def post(self): + greeting = Greeting() + + if users.get_current_user(): + greeting.author = users.get_current_user() + + greeting.content = self.request.get('content') + greeting.put() + self.redirect('/') + + +application = webapp.WSGIApplication([ + ('/', MainPage), + ('/sign', Guestbook) +], debug=True) + + +def main(): + wsgiref.handlers.CGIHandler().run(application) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/dev_appserver.py b/google_appengine/dev_appserver.py new file mode 100755 index 0000000..61cb609 --- /dev/null +++ b/google_appengine/dev_appserver.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Convenience wrapper for starting an appengine tool.""" + + +import os +import sys + +if not hasattr(sys, 'version_info'): + sys.stderr.write('Very old versions of Python are not supported. Please ' + 'use version 2.5 or greater.\n') + sys.exit(1) +version_tuple = tuple(sys.version_info[:2]) +if version_tuple < (2, 4): + sys.stderr.write('Error: Python %d.%d is not supported. Please use ' + 'version 2.5 or greater.\n' % version_tuple) + sys.exit(1) +if version_tuple == (2, 4): + sys.stderr.write('Warning: Python 2.4 is not supported; this program may ' + 'break. Please use version 2.5 or greater.\n') + +DIR_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) +SCRIPT_DIR = os.path.join(DIR_PATH, 'google', 'appengine', 'tools') + +EXTRA_PATHS = [ + DIR_PATH, + os.path.join(DIR_PATH, 'lib', 'antlr3'), + os.path.join(DIR_PATH, 'lib', 'django'), + os.path.join(DIR_PATH, 'lib', 'fancy_urllib'), + os.path.join(DIR_PATH, 'lib', 'ipaddr'), + os.path.join(DIR_PATH, 'lib', 'webob'), + os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'), +] + +SCRIPT_EXCEPTIONS = { + "dev_appserver.py" : "dev_appserver_main.py" +} + + +def fix_sys_path(): + """Fix the sys.path to include our extra paths.""" + sys.path = EXTRA_PATHS + sys.path + + +def run_file(file_path, globals_, script_dir=SCRIPT_DIR): + """Execute the file at the specified path with the passed-in globals.""" + fix_sys_path() + script_name = os.path.basename(file_path) + script_name = SCRIPT_EXCEPTIONS.get(script_name, script_name) + script_path = os.path.join(script_dir, script_name) + execfile(script_path, globals_) + + +if __name__ == '__main__': + run_file(__file__, globals()) diff --git a/google_appengine/google/__init__.py b/google_appengine/google/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/__init__.pyc b/google_appengine/google/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fc59f5d091b6d7c6ca04d6a04645af21e07ac59 GIT binary patch literal 144 zcwW2siI?l7Oo~r30~9a%knvjB+{28Lh_kcgiKNDhrCwgeLT8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZas^`1s7c%#!$cy@JXT4xnzE-29Z%oK!oI I9mOCs0rC7G%>V!Z literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/__init__.py b/google_appengine/google/appengine/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/__init__.pyc b/google_appengine/google/appengine/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..97079b192ae2670808e8c63070a5914c9689aa61 GIT binary patch literal 154 zcwW2siI?l7Oo~r30~9a%knvjB+{28Lh_kcgiKNDhrCb_Npq8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg|_JW?p7Ve7s&kWeEq+9Gl$yl+v73 KJCJR~AkzVYxg@;+ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/__init__.py b/google_appengine/google/appengine/api/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/api/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/api/__init__.pyc b/google_appengine/google/appengine/api/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05da717891073c11b19b4703ef80796d67d20cb4 GIT binary patch literal 158 zcwW2siI;1SOo~r30~9a%knvjB+{28Lh_kcgiKNDhrCb_Wvr8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg|0Jrha^UW?p7Ve7s&kWeEq+ESuc? Ol+v73JCKdVAR7Q6@FpDq literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/api_base_pb.py b/google_appengine/google/appengine/api/api_base_pb.py new file mode 100755 index 0000000..aa30190 --- /dev/null +++ b/google_appengine/google/appengine/api/api_base_pb.py @@ -0,0 +1,582 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class StringProto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.value_)) + return n + 1 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Integer32Proto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = 0 + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.value_) + return n + 1 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_value(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatInt32(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Integer64Proto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = 0 + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.value_) + return n + 1 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_value(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatInt64(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class BoolProto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = 0 + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + return n + 2 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putBoolean(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_value(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatBool(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DoubleProto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = 0.0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = 0.0 + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + return n + 9 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(9) + out.putDouble(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 9: + self.set_value(d.getDouble()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormat(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.DOUBLE, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class BytesProto(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.value_)) + return n + 1 + + def Clear(self): + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kvalue = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "value", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class VoidProto(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['StringProto','Integer32Proto','Integer64Proto','BoolProto','DoubleProto','BytesProto','VoidProto'] diff --git a/google_appengine/google/appengine/api/api_base_pb.pyc b/google_appengine/google/appengine/api/api_base_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d86780c734fe92842a2081fe52e25c79d0c4137d GIT binary patch literal 26446 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 ZIgmU~4GxHDjYM@~f&b2nzY7zm{|7rg$pZiY literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/apiproxy_rpc.py b/google_appengine/google/appengine/api/apiproxy_rpc.py new file mode 100755 index 0000000..04518cc --- /dev/null +++ b/google_appengine/google/appengine/api/apiproxy_rpc.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Base class for implementing RPC of API proxy stubs.""" + + + + + +import sys + + +class RPC(object): + """Base class for implementing RPC of API proxy stubs. + + To implement a RPC to make real asynchronous API call: + - Extend this class. + - Override _MakeCallImpl and/or _WaitImpl to do a real asynchronous call. + """ + + IDLE = 0 + RUNNING = 1 + FINISHING = 2 + + def __init__(self, package=None, call=None, request=None, response=None, + callback=None, deadline=None, stub=None): + """Constructor for the RPC object. + + All arguments are optional, and simply set members on the class. + These data members will be overriden by values passed to MakeCall. + + Args: + package: string, the package for the call + call: string, the call within the package + request: ProtocolMessage instance, appropriate for the arguments + response: ProtocolMessage instance, appropriate for the response + callback: callable, called when call is complete + deadline: A double specifying the deadline for this call as the number of + seconds from the current time. Ignored if non-positive. + stub: APIProxyStub instance, used in default _WaitImpl to do real call + """ + self.__exception = None + self.__state = RPC.IDLE + self.__traceback = None + + self.package = package + self.call = call + self.request = request + self.response = response + self.callback = callback + self.deadline = deadline + self.stub = stub + self.cpu_usage_mcycles = 0 + + def Clone(self): + """Make a shallow copy of this instances attributes, excluding methods. + + This is usually used when an RPC has been specified with some configuration + options and is being used as a template for multiple RPCs outside of a + developer's easy control. + """ + if self.state != RPC.IDLE: + raise AssertionError('Cannot clone a call already in progress') + + clone = self.__class__() + for k, v in self.__dict__.iteritems(): + setattr(clone, k, v) + return clone + + def MakeCall(self, package=None, call=None, request=None, response=None, + callback=None, deadline=None): + """Makes an asynchronous (i.e. non-blocking) API call within the + specified package for the specified call method. + + It will call the _MakeRealCall to do the real job. + + Args: + Same as constructor; see __init__. + + Raises: + TypeError or AssertionError if an argument is of an invalid type. + AssertionError or RuntimeError is an RPC is already in use. + """ + self.callback = callback or self.callback + self.package = package or self.package + self.call = call or self.call + self.request = request or self.request + self.response = response or self.response + self.deadline = deadline or self.deadline + + assert self.__state is RPC.IDLE, ('RPC for %s.%s has already been started' % + (self.package, self.call)) + assert self.callback is None or callable(self.callback) + self._MakeCallImpl() + + def Wait(self): + """Waits on the API call associated with this RPC.""" + rpc_completed = self._WaitImpl() + + assert rpc_completed, ('RPC for %s.%s was not completed, and no other ' + + 'exception was raised ' % (self.package, self.call)) + + def CheckSuccess(self): + """If there was an exception, raise it now. + + Raises: + Exception of the API call or the callback, if any. + """ + if self.exception and self.__traceback: + raise self.exception.__class__, self.exception, self.__traceback + elif self.exception: + raise self.exception + + @property + def exception(self): + return self.__exception + + @property + def state(self): + return self.__state + + def _MakeCallImpl(self): + """Override this method to implement a real asynchronous call rpc.""" + self.__state = RPC.RUNNING + + def _WaitImpl(self): + """Override this method to implement a real asynchronous call rpc. + + Returns: + True if the async call was completed successfully. + """ + try: + try: + self.stub.MakeSyncCall(self.package, self.call, + self.request, self.response) + except Exception: + _, self.__exception, self.__traceback = sys.exc_info() + finally: + self.__state = RPC.FINISHING + self.__Callback() + + return True + + def __Callback(self): + if self.callback: + try: + self.callback() + except: + _, self.__exception, self.__traceback = sys.exc_info() + self.__exception._appengine_apiproxy_rpc = self + raise diff --git a/google_appengine/google/appengine/api/apiproxy_rpc.pyc b/google_appengine/google/appengine/api/apiproxy_rpc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31a91592d5f9f740814970b2b7b9cf36af82322c GIT binary patch literal 6085 zcwW6&;cgqp5#A#yQj{pkcAU6woOT=3F;mFWkD`CvqNyu8t&oH@PA=pMX^G?AQab5) zOW*EUW}pCt?H>i&XXqRBH45}+fdYMxzCioU?Cp_qR3K>~Q{rN8xw|tv^UXK2_xFFT zt^Dm)@@XupPYd6l;cGV0BtjgZ4MfrqCxJLXrzuoJjL~U{S3|W%sa9%DeL6bF1=1+N0Ds(OcCIzj~&MB2APWeukZP zuw)lPWRxe{&~3OMrIrUED$x-8RZ%2{c1y`WX+TW_O}t#Wiyn69?8k(khBy;q7zo=C zX_NacKe!~$0`9cMnH%YdGdE{hoVB>q6=zG_SrKP#?yQQl4tIKl0qU$d^bvH-Um<-* z=f)OgY;hnmxII$N&ZA=$Tehu~_cI);I4sG!20bP9)TTO*vQ09EG-P|YLs>adlaVTn z)H!dpV9v)!3a(8e8`YCeQ*1oKZk3&LIhx65QC2D=r&y>GGOf40SEe{L&T|rz;*;o5 z-2)N|AY_weR)h6%$OqMY_WHTWGy*-~`+RY6HNQ|#p^LpI9~Ii_SZALpV`%RbR*Ujj z;rvrzaayF2olC53Rc~Xau(SH6`}lPW+K-^$J?=%L49lnoGo2p6czhPZu%=M3ep#ZT zBujHhzYmy}SSHO>#p!rPK}B->j7o6oa0?{!uX#z{LgZEZ%|AxPI!_R(g`PNTmPJ7k zXw!+>lDmhwE}&dGmbuRFPPIvG`b=$A`%v`WqhNeQ!Mum31#^_-eE1|$C2=Du?jKX^CsgEuX&r4!q$G-X;2O9H@f63^mHnf|g@%&2S}w$TYmNJ!&h zmzou7NaL`FrZ0SBK0&j6q$g@SkBdleKhURnrlZ7cA8LJ=sUe`H@yDd3 zUl9Rv6$l8K;6)(vKm;_UMfvc{Ks>+8E0#psrjZA~y(Kk_4mJ!wH;a5%Z(cDzhnphOA5o`x9l4bYd!E zdCW@;pjTqAnS&q)R+KX*9w8t`3ePTlQu-OpAq{K~!TC5nEQ^S6#``&rG|Z4Gtt64W zDfWq^RY0?k_6abaB4|kt$g{MD(hklMt+)_=rZPQM#T_FR5)4VRh0a_k8S>Ijl;_%l zZ~=Yn?gBVNm?SfbBY5}_D6N3T#Roy210tksU@3U+gL4bg`e9M%!gd)6%;5+Lhl?bQ zfeZdAwW`2(VmKavezJ0(;1I5RDXfnrn4EC?>=J>?AS8JYX?IdVblwWCGSaL6_p0!tg6t5*BG*ZP0ky3Q(>7?&i? ztMr%zTqiN>Zw2*V<0iuMj!P%R>~%W%k?`rn*Qj%7|n+T%z5ncH22p@yT8pqcAZg384TH z?d!pKbe?Dy&d1?$T-O|W$` z=l3BM5I;gw-}0FQTo$7QwfI8e;+wrB1stf{&4S7G*Mq_C;1_T1`0rxBKfHE)>aP0Z zFT#m5 zF51Q=3VY85gbKb(aZpl%+QVu}QWs@PW{sM6_}!bC8P09s5)>q3%^~UdMEDgS?+$kN ze(CYRMYg+P?)Y0Gjnf$x5&?N|c!`L45BvRzHYVB=?Pkyn-bMmm55A9Dd3BDUx=yC7 zU6K07BABhub8@i%^buji-S*Bw#J|Bh6BW=<`o0hX^Pg}wn9~KDn=6sq)NtWc+tS`3 zYt#oCE*@rXv+xJvGK0*Lc5|owS@;VE7`q9>HG_9?Js$MQpw&ewNA-$}l3O^@Ntw}O zhaVFi4~MRfd!HJ*z;Q7b(kqDYCN;OH`7Slz^PAEeBGjagl9~26_}QOf|C|9X#R?2- zl4wYQcDvnZ2lxYMuD#dY=mi@$yPfXpMr)(hZFR4?n;?~7>{d%&cCQt~^J-p-u57TM PIJ^Ip++td+bFcXy93(request, response). + """ + + def __init__(self, service_name, max_request_size=MAX_REQUEST_SIZE): + """Constructor. + + Args: + service_name: Service name expected for all calls. + max_request_size: int, maximum allowable size of the incoming request. A + apiproxy_errors.RequestTooLargeError will be raised if the inbound + request exceeds this size. Default is 1 MB. + """ + self.__service_name = service_name + self.__max_request_size = max_request_size + + def CreateRPC(self): + """Creates RPC object instance. + + Returns: + a instance of RPC. + """ + return apiproxy_rpc.RPC(stub=self) + + def MakeSyncCall(self, service, call, request, response): + """The main RPC entry point. + + Args: + service: Must be name as provided to service_name of constructor. + call: A string representing the rpc to make. Must be part of + the underlying services methods and impemented by _Dynamic_. + request: A protocol buffer of the type corresponding to 'call'. + response: A protocol buffer of the type corresponding to 'call'. + """ + assert service == self.__service_name, ('Expected "%s" service name, ' + 'was "%s"' % (self.__service_name, + service)) + if request.ByteSize() > self.__max_request_size: + raise apiproxy_errors.RequestTooLargeError( + 'The request to API call %s.%s() was too large.' % (service, call)) + messages = [] + assert request.IsInitialized(messages), messages + + method = getattr(self, '_Dynamic_' + call) + method(request, response) diff --git a/google_appengine/google/appengine/api/apiproxy_stub.pyc b/google_appengine/google/appengine/api/apiproxy_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..11c942d92a6d92278332111b499bbb77994f0c0d GIT binary patch literal 2872 zcwV(t-EQMV6h3wmr_FYu0@SWx5u+8=W~Gt{iL0ur=uZ%p+ESb%ExEBAdy-CTd(6zF zZBX`RySKao@53dp!VADTV>``umkWd%DI?FEpZU(;H-G;1xb?@=_$Y$%*TDa`_}Aaz z5(5lybs%=((t*T!3bqdcmr??@bjPv@e=ggE%LXC z%U-BCi;_@lHj;{oG*5WSGb6Gw+dVyGxso?irp;uiJK}HrwH>rFE4YNYu+l)~QE~d= z;we{3DlJGLKB;WbbAnK>7_uPEn2{_e z5F%q?j-GNQLc>|iN8uzfOmlT5BF?ff<&~4eIev~uGIL-C5a5SvKO9PDzfC%N1GBb|_C`%2Bb7}D zW4mgsi?CS*Cm^s}tiJ%A@td4ShQ~J9VUn;2mohDkrQuCb!VI+d6^%uPfbm$Qla%!3 zbvR5oBO#L`%q&NhNTyV-(z1hKN`H*qYpRZ4=$uJ;6sj@r(J8wY2xQ2a3WY{!qVhYG zlPsRQD&1oU5$Bj>TU7#r7ao*l!jrGr@m>+niOrY!43}WBeS=^zLFS8h<@fW@rUDt; zvpgBud3ejkDjvLJLjz(LeS}N*T&BF6MJklt19_b#GK_V1Eaf=i0d^eE#vu zOb#!wH8AbkgjvMPzVf-5sBGR^;apDTN3$Z;MQ#wCb?p-xLl98G5{%!dlJP(pS)K=j zSAIiR5D@Spy10q|dXc;r$;UL6_+*f$G`ys_t1in!WzgY#R!I|Qm#bt-hULBbjv$eh zk-sf_BDCRjfiDTcP6el&C_;pR7#T@qipgliRYgB$n&Sf^Rl%yU#VOfy^78!7OToDR z%Z>gL%hId(^Vx=e_SUPg$*wWmbgVm2l&)edbVTnH`C=Qov!UBBtg?}kB}9WA+rRS^ zoV01ceM>qY;IfO)o-#samM-n|?@bNwBg^>I*S>wj>8XAny_&};IMj#879vd0CN?w> zV{SrYRJ&P?r#&qrvhz%M?O_>*y|_xH-K1RWaEv0)Zm6~oIpoB<$JluNX%_7xxNW+H z!iLtG<2jEUTG#Rasr#{G11p)5>JtQ}WjCpeCkbtvb{oqGzlkP~cV7m6@5i5d{j;Ed zIOzGd3Xf=c0*mzZFyZ;cSo9cay4P!Z?#jyY(g{-1DijStFPSZK;jc6IhhL%~-6!vp ostL|-%59tYPf3mHRq(yN^`S$vW!Nqfzw^;NL~qqut$DS709mID8UO$Q literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/apiproxy_stub_map.py b/google_appengine/google/appengine/api/apiproxy_stub_map.py new file mode 100755 index 0000000..46495c5 --- /dev/null +++ b/google_appengine/google/appengine/api/apiproxy_stub_map.py @@ -0,0 +1,513 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Container of APIProxy stubs for more convenient unittesting. + +Classes/variables/functions defined here: + APIProxyStubMap: container of APIProxy stubs. + apiproxy: global instance of an APIProxyStubMap. + MakeSyncCall: APIProxy entry point. + UserRPC: User-visible class wrapping asynchronous RPCs. +""" + + + + + +import inspect +import sys + +from google.appengine.api import apiproxy_rpc + + +def CreateRPC(service): + """Creates a RPC instance for the given service. + + The instance is suitable for talking to remote services. + Each RPC instance can be used only once, and should not be reused. + + Args: + service: string representing which service to call. + + Returns: + the rpc object. + + Raises: + AssertionError or RuntimeError if the stub for service doesn't supply a + CreateRPC method. + """ + stub = apiproxy.GetStub(service) + assert stub, 'No api proxy found for service "%s"' % service + assert hasattr(stub, 'CreateRPC'), (('The service "%s" doesn\'t have ' + + 'a CreateRPC method.') % service) + return stub.CreateRPC() + + +def MakeSyncCall(service, call, request, response): + """The APIProxy entry point for a synchronous API call. + + Args: + service: string representing which service to call + call: string representing which function to call + request: protocol buffer for the request + response: protocol buffer for the response + + Returns: + Response protocol buffer or None. Some implementations may return + a response protocol buffer instead of modifying 'response'. + Caller must use returned value in such cases. If 'response' is modified + then returns None. + + Raises: + apiproxy_errors.Error or a subclass. + """ + return apiproxy.MakeSyncCall(service, call, request, response) + + +class ListOfHooks(object): + """An ordered collection of hooks for a particular API call. + + A hook is a function that has exactly the same signature as + a service stub. It will be called before or after an api hook is + executed, depending on whether this list is for precall of postcall hooks. + Hooks can be used for debugging purposes (check certain + pre- or postconditions on api calls) or to apply patches to protocol + buffers before/after a call gets submitted. + """ + + def __init__(self): + """Constructor.""" + + self.__content = [] + + self.__unique_keys = set() + + def __len__(self): + """Returns the amount of elements in the collection.""" + return self.__content.__len__() + + def __Insert(self, index, key, function, service=None): + """Appends a hook at a certain position in the list. + + Args: + index: the index of where to insert the function + key: a unique key (within the module) for this particular function. + If something from the same module with the same key is already + registered, nothing will be added. + function: the hook to be added. + service: optional argument that restricts the hook to a particular api + + Returns: + True if the collection was modified. + """ + unique_key = (key, inspect.getmodule(function)) + if unique_key in self.__unique_keys: + return False + num_args = len(inspect.getargspec(function)[0]) + if (inspect.ismethod(function)): + num_args -= 1 + self.__content.insert(index, (key, function, service, num_args)) + self.__unique_keys.add(unique_key) + return True + + def Append(self, key, function, service=None): + """Appends a hook at the end of the list. + + Args: + key: a unique key (within the module) for this particular function. + If something from the same module with the same key is already + registered, nothing will be added. + function: the hook to be added. + service: optional argument that restricts the hook to a particular api + + Returns: + True if the collection was modified. + """ + return self.__Insert(len(self), key, function, service) + + def Push(self, key, function, service=None): + """Inserts a hook at the beginning of the list. + + Args: + key: a unique key (within the module) for this particular function. + If something from the same module with the same key is already + registered, nothing will be added. + function: the hook to be added. + service: optional argument that restricts the hook to a particular api + + Returns: + True if the collection was modified. + """ + return self.__Insert(0, key, function, service) + + def Clear(self): + """Removes all hooks from the list (useful for unit tests).""" + self.__content = [] + self.__unique_keys = set() + + def Call(self, service, call, request, response, rpc=None, error=None): + """Invokes all hooks in this collection. + + NOTE: For backwards compatibility, if error is not None, hooks + with 4 or 5 arguments are *not* called. This situation + (error=None) only occurs when the RPC request raised an exception; + in the past no hooks would be called at all in that case. + + Args: + service: string representing which service to call + call: string representing which function to call + request: protocol buffer for the request + response: protocol buffer for the response + rpc: optional RPC used to make this call + error: optional Exception instance to be passed as 6th argument + """ + for key, function, srv, num_args in self.__content: + if srv is None or srv == service: + if num_args == 6: + function(service, call, request, response, rpc, error) + elif error is not None: + pass + elif num_args == 5: + function(service, call, request, response, rpc) + else: + function(service, call, request, response) + + +class APIProxyStubMap(object): + """Container of APIProxy stubs for more convenient unittesting. + + Stubs may be either trivial implementations of APIProxy services (e.g. + DatastoreFileStub, UserServiceStub) or "real" implementations. + + For unittests, we may want to mix and match real and trivial implementations + of services in order to better focus testing on individual service + implementations. To achieve this, we allow the client to attach stubs to + service names, as well as define a default stub to be used if no specific + matching stub is identified. + """ + + + def __init__(self, default_stub=None): + """Constructor. + + Args: + default_stub: optional stub + + 'default_stub' will be used whenever no specific matching stub is found. + """ + self.__stub_map = {} + self.__default_stub = default_stub + self.__precall_hooks = ListOfHooks() + self.__postcall_hooks = ListOfHooks() + + def GetPreCallHooks(self): + """Gets a collection for all precall hooks.""" + return self.__precall_hooks + + def GetPostCallHooks(self): + """Gets a collection for all precall hooks.""" + return self.__postcall_hooks + + def RegisterStub(self, service, stub): + """Register the provided stub for the specified service. + + Args: + service: string + stub: stub + """ + assert not self.__stub_map.has_key(service), repr(service) + self.__stub_map[service] = stub + + if service == 'datastore': + self.RegisterStub('datastore_v3', stub) + + def GetStub(self, service): + """Retrieve the stub registered for the specified service. + + Args: + service: string + + Returns: + stub + + Returns the stub registered for 'service', and returns the default stub + if no such stub is found. + """ + return self.__stub_map.get(service, self.__default_stub) + + def MakeSyncCall(self, service, call, request, response): + """The APIProxy entry point. + + Args: + service: string representing which service to call + call: string representing which function to call + request: protocol buffer for the request + response: protocol buffer for the response + + Returns: + Response protocol buffer or None. Some implementations may return + a response protocol buffer instead of modifying 'response'. + Caller must use returned value in such cases. If 'response' is modified + then returns None. + + Raises: + apiproxy_errors.Error or a subclass. + """ + stub = self.GetStub(service) + assert stub, 'No api proxy found for service "%s"' % service + if hasattr(stub, 'CreateRPC'): + rpc = stub.CreateRPC() + self.__precall_hooks.Call(service, call, request, response, rpc) + try: + rpc.MakeCall(service, call, request, response) + rpc.Wait() + rpc.CheckSuccess() + except Exception, err: + self.__postcall_hooks.Call(service, call, request, response, rpc, err) + raise + else: + self.__postcall_hooks.Call(service, call, request, response, rpc) + else: + self.__precall_hooks.Call(service, call, request, response) + try: + returned_response = stub.MakeSyncCall(service, call, request, response) + except Exception, err: + self.__postcall_hooks.Call(service, call, request, response, None, err) + raise + else: + self.__postcall_hooks.Call(service, call, request, + returned_response or response) + return returned_response + + +class UserRPC(object): + """Wrapper class for asynchronous RPC. + + Simplest low-level usage pattern: + + rpc = UserRPC('service', [deadline], [callback]) + rpc.make_call('method', request, response) + . + . + . + rpc.wait() + rpc.check_success() + + However, a service module normally provides a wrapper so that the + typical usage pattern becomes more like this: + + from google.appengine.api import service + rpc = service.create_rpc([deadline], [callback]) + service.make_method_call(rpc, [service-specific-args]) + . + . + . + rpc.wait() + result = rpc.get_result() + + The service.make_method_call() function sets a service- and method- + specific hook function that is called by rpc.get_result() with the + rpc object as its first argument, and service-specific value as its + second argument. The hook function should call rpc.check_success() + and then extract the user-level result from the rpc.result + protobuffer. Additional arguments may be passed from + make_method_call() to the get_result hook via the second argument. + """ + + __method = None + __get_result_hook = None + __user_data = None + __postcall_hooks_called = False + + def __init__(self, service, deadline=None, callback=None): + """Constructor. + + Args: + service: The service name. + deadline: Optional deadline. Default depends on the implementation. + callback: Optional argument-less callback function. + """ + self.__service = service + self.__rpc = CreateRPC(service) + self.__rpc.deadline = deadline + self.__rpc.callback = callback + + @property + def service(self): + """Return the service name.""" + return self.__service + + @property + def method(self): + """Return the method name.""" + return self.__method + + @property + def deadline(self): + """Return the deadline, if set explicitly (otherwise None).""" + return self.__rpc.deadline + + def __get_callback(self): + """Return the callback attribute, a function without arguments. + + This attribute can also be assigned to. For example, the + following code calls some_other_function(rpc) when the RPC is + complete: + + rpc = service.create_rpc() + rpc.callback = lambda: some_other_function(rpc) + service.make_method_call(rpc) + rpc.wait() + """ + return self.__rpc.callback + def __set_callback(self, callback): + """Set the callback function.""" + self.__rpc.callback = callback + callback = property(__get_callback, __set_callback) + + @property + def request(self): + """Return the request protocol buffer object.""" + return self.__rpc.request + + @property + def response(self): + """Return the response protocol buffer object.""" + return self.__rpc.response + + @property + def state(self): + """Return the RPC state. + + Possible values are attributes of apiproxy_rpc.RPC: IDLE, RUNNING, + FINISHING. + """ + return self.__rpc.state + + @property + def get_result_hook(self): + """Return the get-result hook function.""" + return self.__get_result_hook + + @property + def user_data(self): + """Return the user data for the hook function.""" + return self.__user_data + + def make_call(self, method, request, response, + get_result_hook=None, user_data=None): + """Initiate a call. + + Args: + method: The method name. + request: The request protocol buffer. + response: The response protocol buffer. + get_result_hook: Optional get-result hook function. If not None, + this must be a function with exactly one argument, the RPC + object (self). Its return value is returned from get_result(). + user_data: Optional additional arbitrary data for the get-result + hook function. This can be accessed as rpc.user_data. The + type of this value is up to the service module. + + This function may only be called once per RPC object. It sends + the request to the remote server, but does not wait for a + response. This allows concurrent execution of the remote call and + further local processing (e.g., making additional remote calls). + + Before the call is initiated, the precall hooks are called. + """ + assert self.__rpc.state == apiproxy_rpc.RPC.IDLE, repr(self.state) + self.__method = method + self.__get_result_hook = get_result_hook + self.__user_data = user_data + apiproxy.GetPreCallHooks().Call( + self.__service, method, request, response, self.__rpc) + self.__rpc.MakeCall(self.__service, method, request, response) + + def wait(self): + """Wait for the call to complete, and call callbacks. + + This is the only time callback functions may be called. (However, + note that check_success() and get_result() call wait().) Waiting + for one RPC may cause callbacks for other RPCs to be called. + Callback functions may call check_success() and get_result(). + + Callbacks are called without arguments; if a callback needs access + to the RPC object a Python nested function (a.k.a. closure) or a + bound may be used. To facilitate this, the callback may be + assigned after the RPC object is created (but before make_call() + is called). + + Note: don't confuse callbacks with get-result hooks or precall + and postcall hooks. + """ + assert self.__rpc.state != apiproxy_rpc.RPC.IDLE, repr(self.state) + if self.__rpc.state == apiproxy_rpc.RPC.RUNNING: + self.__rpc.Wait() + assert self.__rpc.state == apiproxy_rpc.RPC.FINISHING, repr(self.state) + + def check_success(self): + """Check for success of the RPC, possibly raising an exception. + + This function should be called at least once per RPC. If wait() + hasn't been called yet, it is called first. If the RPC caused + an exceptional condition, an exception will be raised here. + The first time check_success() is called, the postcall hooks + are called. + """ + self.wait() + try: + self.__rpc.CheckSuccess() + except Exception, err: + if not self.__postcall_hooks_called: + self.__postcall_hooks_called = True + apiproxy.GetPostCallHooks().Call(self.__service, self.__method, + self.request, self.response, + self.__rpc, err) + raise + else: + if not self.__postcall_hooks_called: + self.__postcall_hooks_called = True + apiproxy.GetPostCallHooks().Call(self.__service, self.__method, + self.request, self.response, self.__rpc) + + def get_result(self): + """Get the result of the RPC, or possibly raise an exception. + + This implies a call to check_success(). If a get-result hook was + passed to make_call(), that hook is responsible for calling + check_success(), and the return value of the hook is returned. + Otherwise, check_success() is called directly and None is + returned. + """ + if self.__get_result_hook is None: + self.check_success() + return None + else: + return self.__get_result_hook(self) + + +def GetDefaultAPIProxy(): + try: + runtime = __import__('google.appengine.runtime', globals(), locals(), + ['apiproxy']) + return APIProxyStubMap(runtime.apiproxy) + except (AttributeError, ImportError): + return APIProxyStubMap() + + +apiproxy = GetDefaultAPIProxy() diff --git a/google_appengine/google/appengine/api/apiproxy_stub_map.pyc b/google_appengine/google/appengine/api/apiproxy_stub_map.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a54ebc7d77c9e89a33e542e77b30f3347bae32f5 GIT binary patch literal 20354 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 logging.CRITICAL: + return 4 + elif level >= logging.ERROR: + return 3 + elif level >= logging.WARNING: + return 2 + elif level >= logging.INFO: + return 1 + else: + return 0 diff --git a/google_appengine/google/appengine/api/appinfo.py b/google_appengine/google/appengine/api/appinfo.py new file mode 100755 index 0000000..7861833 --- /dev/null +++ b/google_appengine/google/appengine/api/appinfo.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""AppInfo tools. + +Library for working with AppInfo records in memory, store and load from +configuration files. +""" + + + + + +import logging +import re + +from google.appengine.api import appinfo_errors +from google.appengine.api import validation +from google.appengine.api import yaml_builder +from google.appengine.api import yaml_listener +from google.appengine.api import yaml_object + + +_URL_REGEX = r'(?!\^)/|\.|(\(.).*(?!\$).' +_FILES_REGEX = r'(?!\^).*(?!\$).' + +_DELTA_REGEX = r'([0-9]+)([DdHhMm]|[sS]?)' +_EXPIRATION_REGEX = r'\s*(%s)(\s+%s)*\s*' % (_DELTA_REGEX, _DELTA_REGEX) + +_SERVICE_RE_STRING = r'(mail|xmpp_message|rest|warmup)' + +_PAGE_NAME_REGEX = r'^.+$' + + +_EXPIRATION_CONVERSIONS = { + 'd': 60 * 60 * 24, + 'h': 60 * 60, + 'm': 60, + 's': 1, +} + +APP_ID_MAX_LEN = 100 +MAJOR_VERSION_ID_MAX_LEN = 100 +MAX_URL_MAPS = 100 + +PARTITION_SEPARATOR = '~' + +DOMAIN_SEPARATOR = ':' + +PARTITION_RE_STRING = (r'[a-z\d\-]{1,%d}\%s' % + (APP_ID_MAX_LEN, PARTITION_SEPARATOR)) +DOMAIN_RE_STRING = (r'(?!\-)[a-z\d\-\.]{1,%d}%s' % + (APP_ID_MAX_LEN, DOMAIN_SEPARATOR)) +DISPLAY_APP_ID_RE_STRING = (r'(?!-)[a-z\d\-]{1,%d}' % (APP_ID_MAX_LEN)) +APPLICATION_RE_STRING = (r'(?:%s)?(?:%s)?%s' % + (PARTITION_RE_STRING, + DOMAIN_RE_STRING, + DISPLAY_APP_ID_RE_STRING)) +VERSION_RE_STRING = r'(?!-)[a-z\d\-]{1,%d}' % MAJOR_VERSION_ID_MAX_LEN + +RUNTIME_RE_STRING = r'[a-z]{1,30}' + +API_VERSION_RE_STRING = r'[\w.]{1,32}' + +HANDLER_STATIC_FILES = 'static_files' +HANDLER_STATIC_DIR = 'static_dir' +HANDLER_SCRIPT = 'script' + +LOGIN_OPTIONAL = 'optional' +LOGIN_REQUIRED = 'required' +LOGIN_ADMIN = 'admin' + +AUTH_FAIL_ACTION_REDIRECT = 'redirect' +AUTH_FAIL_ACTION_UNAUTHORIZED = 'unauthorized' + +SECURE_HTTP = 'never' +SECURE_HTTPS = 'always' +SECURE_HTTP_OR_HTTPS = 'optional' +SECURE_DEFAULT = 'default' + +REQUIRE_MATCHING_FILE = 'require_matching_file' + +DEFAULT_SKIP_FILES = (r'^(.*/)?(' + r'(app\.yaml)|' + r'(app\.yml)|' + r'(index\.yaml)|' + r'(index\.yml)|' + r'(#.*#)|' + r'(.*~)|' + r'(.*\.py[co])|' + r'(.*/RCS/.*)|' + r'(\..*)|' + r')$') + +LOGIN = 'login' +AUTH_FAIL_ACTION = 'auth_fail_action' +SECURE = 'secure' +URL = 'url' +STATIC_FILES = 'static_files' +UPLOAD = 'upload' +STATIC_DIR = 'static_dir' +MIME_TYPE = 'mime_type' +SCRIPT = 'script' +EXPIRATION = 'expiration' + +APPLICATION = 'application' +VERSION = 'version' +RUNTIME = 'runtime' +API_VERSION = 'api_version' +HANDLERS = 'handlers' +DEFAULT_EXPIRATION = 'default_expiration' +SKIP_FILES = 'skip_files' +SERVICES = 'inbound_services' +DERIVED_FILE_TYPE = 'derived_file_type' +JAVA_PRECOMPILED = 'java_precompiled' +PYTHON_PRECOMPILED = 'python_precompiled' +ADMIN_CONSOLE = 'admin_console' +ERROR_HANDLERS = 'error_handlers' + +PAGES = 'pages' +NAME = 'name' + +ERROR_CODE = 'error_code' +FILE = 'file' +_ERROR_CODE_REGEX = r'(default|over_quota|dos_api_denial|timeout)' + + +class URLMap(validation.Validated): + """Mapping from URLs to handlers. + + This class acts like something of a union type. Its purpose is to + describe a mapping between a set of URLs and their handlers. What + handler type a given instance has is determined by which handler-id + attribute is used. + + Each mapping can have one and only one handler type. Attempting to + use more than one handler-id attribute will cause an UnknownHandlerType + to be raised during validation. Failure to provide any handler-id + attributes will cause MissingHandlerType to be raised during validation. + + The regular expression used by the url field will be used to match against + the entire URL path and query string of the request. This means that + partial maps will not be matched. Specifying a url, say /admin, is the + same as matching against the regular expression '^/admin$'. Don't begin + your matching url with ^ or end them with $. These regular expressions + won't be accepted and will raise ValueError. + + Attributes: + login: Whether or not login is required to access URL. Defaults to + 'optional'. + secure: Restriction on the protocol which can be used to serve + this URL/handler (HTTP, HTTPS or either). + url: Regular expression used to fully match against the request URLs path. + See Special Cases for using static_dir. + static_files: Handler id attribute that maps URL to the appropriate + file. Can use back regex references to the string matched to url. + upload: Regular expression used by the application configuration + program to know which files are uploaded as blobs. It's very + difficult to determine this using just the url and static_files + so this attribute must be included. Required when defining a + static_files mapping. + A matching file name must fully match against the upload regex, similar + to how url is matched against the request path. Do not begin upload + with ^ or end it with $. + static_dir: Handler id that maps the provided url to a sub-directory + within the application directory. See Special Cases. + mime_type: When used with static_files and static_dir the mime-type + of files served from those directories are overridden with this + value. + script: Handler id that maps URLs to scipt handler within the application + directory that will run using CGI. + expiration: When used with static files and directories, the time delta to + use for cache expiration. Has the form '4d 5h 30m 15s', where each letter + signifies days, hours, minutes, and seconds, respectively. The 's' for + seconds may be omitted. Only one amount must be specified, combining + multiple amounts is optional. Example good values: '10', '1d 6h', + '1h 30m', '7d 7d 7d', '5m 30'. + + Special cases: + When defining a static_dir handler, do not use a regular expression + in the url attribute. Both the url and static_dir attributes are + automatically mapped to these equivalents: + + /(.*) + /\1 + + For example: + + url: /images + static_dir: images_folder + + Is the same as this static_files declaration: + + url: /images/(.*) + static_files: images/\1 + upload: images/(.*) + """ + + ATTRIBUTES = { + + URL: validation.Optional(_URL_REGEX), + LOGIN: validation.Options(LOGIN_OPTIONAL, + LOGIN_REQUIRED, + LOGIN_ADMIN, + default=LOGIN_OPTIONAL), + + AUTH_FAIL_ACTION: validation.Options(AUTH_FAIL_ACTION_REDIRECT, + AUTH_FAIL_ACTION_UNAUTHORIZED, + default=AUTH_FAIL_ACTION_REDIRECT), + + SECURE: validation.Options(SECURE_HTTP, + SECURE_HTTPS, + SECURE_HTTP_OR_HTTPS, + SECURE_DEFAULT, + default=SECURE_DEFAULT), + + + + HANDLER_STATIC_FILES: validation.Optional(_FILES_REGEX), + UPLOAD: validation.Optional(_FILES_REGEX), + + + HANDLER_STATIC_DIR: validation.Optional(_FILES_REGEX), + + + MIME_TYPE: validation.Optional(str), + EXPIRATION: validation.Optional(_EXPIRATION_REGEX), + + + HANDLER_SCRIPT: validation.Optional(_FILES_REGEX), + + REQUIRE_MATCHING_FILE: validation.Optional(bool), + } + + COMMON_FIELDS = set([URL, LOGIN, AUTH_FAIL_ACTION, SECURE]) + + ALLOWED_FIELDS = { + HANDLER_STATIC_FILES: (MIME_TYPE, UPLOAD, EXPIRATION, + REQUIRE_MATCHING_FILE), + HANDLER_STATIC_DIR: (MIME_TYPE, EXPIRATION, REQUIRE_MATCHING_FILE), + HANDLER_SCRIPT: (), + } + + def GetHandler(self): + """Get handler for mapping. + + Returns: + Value of the handler (determined by handler id attribute). + """ + return getattr(self, self.GetHandlerType()) + + def GetHandlerType(self): + """Get handler type of mapping. + + Returns: + Handler type determined by which handler id attribute is set. + + Raises: + UnknownHandlerType when none of the no handler id attributes + are set. + + UnexpectedHandlerAttribute when an unexpected attribute + is set for the discovered handler type. + + HandlerTypeMissingAttribute when the handler is missing a + required attribute for its handler type. + """ + for id_field in URLMap.ALLOWED_FIELDS.iterkeys(): + if getattr(self, id_field) is not None: + mapping_type = id_field + break + else: + raise appinfo_errors.UnknownHandlerType( + 'Unknown url handler type.\n%s' % str(self)) + + allowed_fields = URLMap.ALLOWED_FIELDS[mapping_type] + + for attribute in self.ATTRIBUTES.iterkeys(): + if (getattr(self, attribute) is not None and + not (attribute in allowed_fields or + attribute in URLMap.COMMON_FIELDS or + attribute == mapping_type)): + raise appinfo_errors.UnexpectedHandlerAttribute( + 'Unexpected attribute "%s" for mapping type %s.' % + (attribute, mapping_type)) + + if mapping_type == HANDLER_STATIC_FILES and not self.upload: + raise appinfo_errors.MissingHandlerAttribute( + 'Missing "%s" attribute for URL "%s".' % (UPLOAD, self.url)) + + return mapping_type + + def CheckInitialized(self): + """Adds additional checking to make sure handler has correct fields. + + In addition to normal ValidatedCheck calls GetHandlerType + which validates all the handler fields are configured + properly. + + Raises: + UnknownHandlerType when none of the no handler id attributes + are set. + + UnexpectedHandlerAttribute when an unexpected attribute + is set for the discovered handler type. + + HandlerTypeMissingAttribute when the handler is missing a + required attribute for its handler type. + """ + super(URLMap, self).CheckInitialized() + self.GetHandlerType() + + +class AdminConsolePage(validation.Validated): + """Class representing admin console page in AdminConsole object. + """ + ATTRIBUTES = { + URL: _URL_REGEX, + NAME: _PAGE_NAME_REGEX, + } + + +class AdminConsole(validation.Validated): + """Class representing admin console directives in application info. + """ + ATTRIBUTES = { + PAGES: validation.Optional(validation.Repeated(AdminConsolePage)), + } + + + +class ErrorHandlers(validation.Validated): + """Class representing error handler directives in application info. + """ + ATTRIBUTES = { + ERROR_CODE: validation.Optional(_ERROR_CODE_REGEX), + FILE: _FILES_REGEX, + MIME_TYPE: validation.Optional(str), + } + + +class AppInfoExternal(validation.Validated): + """Class representing users application info. + + This class is passed to a yaml_object builder to provide the validation + for the application information file format parser. + + Attributes: + application: Unique identifier for application. + version: Application's major version number. + runtime: Runtime used by application. + api_version: Which version of APIs to use. + handlers: List of URL handlers. + default_expiration: Default time delta to use for cache expiration for + all static files, unless they have their own specific 'expiration' set. + See the URLMap.expiration field's documentation for more information. + skip_files: An re object. Files that match this regular expression will + not be uploaded by appcfg.py. For example: + skip_files: | + .svn.*| + #.*# + """ + + ATTRIBUTES = { + + + APPLICATION: APPLICATION_RE_STRING, + VERSION: VERSION_RE_STRING, + RUNTIME: RUNTIME_RE_STRING, + + + API_VERSION: API_VERSION_RE_STRING, + HANDLERS: validation.Optional(validation.Repeated(URLMap)), + + SERVICES: validation.Optional(validation.Repeated( + validation.Regex(_SERVICE_RE_STRING))), + DEFAULT_EXPIRATION: validation.Optional(_EXPIRATION_REGEX), + SKIP_FILES: validation.RegexStr(default=DEFAULT_SKIP_FILES), + DERIVED_FILE_TYPE: validation.Optional(validation.Repeated( + validation.Options(JAVA_PRECOMPILED, PYTHON_PRECOMPILED))), + ADMIN_CONSOLE: validation.Optional(AdminConsole), + ERROR_HANDLERS: validation.Optional(validation.Repeated(ErrorHandlers)), + } + + def CheckInitialized(self): + """Ensures that at least one url mapping is provided. + + Raises: + MissingURLMapping when no URLMap objects are present in object. + TooManyURLMappings when there are too many URLMap entries. + """ + super(AppInfoExternal, self).CheckInitialized() + if not self.handlers: + raise appinfo_errors.MissingURLMapping( + 'No URLMap entries found in application configuration') + if len(self.handlers) > MAX_URL_MAPS: + raise appinfo_errors.TooManyURLMappings( + 'Found more than %d URLMap entries in application configuration' % + MAX_URL_MAPS) + + def FixSecureDefaults(self): + """Force omitted 'secure: ...' handler fields to 'secure: optional'. + + The effect is that handler.secure is never equal to the (nominal) + default. + + See http://b/issue?id=2073962. + """ + if self.handlers: + for handler in self.handlers: + if handler.secure == SECURE_DEFAULT: + handler.secure = SECURE_HTTP_OR_HTTPS + + def WarnReservedURLs(self): + """Generates a warning for reserved URLs. + + See: + http://code.google.com/appengine/docs/python/config/appconfig.html#Reserved_URLs + """ + if self.handlers: + for handler in self.handlers: + if handler.url == '/form': + logging.warning( + 'The URL path "/form" is reserved and will not be matched.') + + +def LoadSingleAppInfo(app_info): + """Load a single AppInfo object where one and only one is expected. + + Args: + app_info: A file-like object or string. If it is a string, parse it as + a configuration file. If it is a file-like object, read in data and + parse. + + Returns: + An instance of AppInfoExternal as loaded from a YAML file. + + Raises: + ValueError: if a specified service is not valid. + EmptyConfigurationFile: when there are no documents in YAML file. + MultipleConfigurationFile: when there is more than one document in YAML + file. + """ + builder = yaml_object.ObjectBuilder(AppInfoExternal) + handler = yaml_builder.BuilderHandler(builder) + listener = yaml_listener.EventListener(handler) + listener.Parse(app_info) + + app_infos = handler.GetResults() + if len(app_infos) < 1: + raise appinfo_errors.EmptyConfigurationFile() + if len(app_infos) > 1: + raise appinfo_errors.MultipleConfigurationFile() + + app_infos[0].FixSecureDefaults() + app_infos[0].WarnReservedURLs() + + return app_infos[0] + + +def ParseExpiration(expiration): + """Parses an expiration delta string. + + Args: + expiration: String that matches _DELTA_REGEX. + + Returns: + Time delta in seconds. + """ + delta = 0 + for match in re.finditer(_DELTA_REGEX, expiration): + amount = int(match.group(1)) + units = _EXPIRATION_CONVERSIONS.get(match.group(2).lower(), 1) + delta += amount * units + return delta + + + +_file_path_positive_re = re.compile(r'^[ 0-9a-zA-Z\._\+/\$-]{1,256}$') + +_file_path_negative_1_re = re.compile(r'\.\.|^\./|\.$|/\./|^-|^_ah/') + +_file_path_negative_2_re = re.compile(r'//|/$') + +_file_path_negative_3_re = re.compile(r'^ | $|/ | /') + + +def ValidFilename(filename): + """Determines if filename is valid. + + filename must be a valid pathname. + - It must contain only letters, numbers, _, +, /, $, ., and -. + - It must be less than 256 chars. + - It must not contain "/./", "/../", or "//". + - It must not end in "/". + - All spaces must be in the middle of a directory or file name. + + Args: + filename: The filename to validate. + + Returns: + An error string if the filename is invalid. Returns '' if the filename + is valid. + """ + if _file_path_positive_re.match(filename) is None: + return 'Invalid character in filename: %s' % filename + if _file_path_negative_1_re.search(filename) is not None: + return ('Filename cannot contain "." or ".." ' + 'or start with "-" or "_ah/": %s' % + filename) + if _file_path_negative_2_re.search(filename) is not None: + return 'Filename cannot have trailing / or contain //: %s' % filename + if _file_path_negative_3_re.search(filename) is not None: + return 'Any spaces must be in the middle of a filename: %s' % filename + return '' diff --git a/google_appengine/google/appengine/api/appinfo.pyc b/google_appengine/google/appengine/api/appinfo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4bbc7411e7457d173b4e698c44d98ad99598ea69 GIT binary patch literal 17042 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 I1^zAmH?7yf{r~^~ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/appinfo_errors.py b/google_appengine/google/appengine/api/appinfo_errors.py new file mode 100755 index 0000000..a79c623 --- /dev/null +++ b/google_appengine/google/appengine/api/appinfo_errors.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Errors used in the Python appinfo API, used by app developers.""" + + + + + +class Error(Exception): + """Base datastore AppInfo type.""" + +class EmptyConfigurationFile(Error): + """Tried to load empty configuration file""" + +class MultipleConfigurationFile(Error): + """Tried to load configuration file with multiple AppInfo objects""" + +class UnknownHandlerType(Error): + """Raised when it is not possible to determine URL mapping type.""" + +class UnexpectedHandlerAttribute(Error): + """Raised when a handler type has an attribute that it does not use.""" + +class MissingHandlerAttribute(Error): + """Raised when a handler is missing an attribute required by its type.""" + +class MissingURLMapping(Error): + """Raised when there are no URL mappings in external appinfo.""" + +class TooManyURLMappings(Error): + """Raised when there are too many URL mappings in external appinfo.""" diff --git a/google_appengine/google/appengine/api/appinfo_errors.pyc b/google_appengine/google/appengine/api/appinfo_errors.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8fa6def770bf663c768e265b10382f9e116fbd5 GIT binary patch literal 2582 zcwWtx-%k@k5XZMIv`_`X2mD3Mniy@2(s!eTiWo_#Aq5`frP+3;?dsj`dAk+*tbdh> ze}w;ve}OZ5z3TxXsT!rs@u!4i3y1xNMlyDcw!omk zdIctn>?>nm7<+$QWU|Dd=+(JQEipNts^v_bW3rN}3z<64swA)&Q&~Ce`pM*vZ1DaO+RGMzPU85x@#x~K^2PTBN?#9B@Uz-yh7$I%F zZ%iMb#sEO~m4>lUp9msS^>NT`@K`pYQH$K(Cf7wY<$_zIh{MQ@wvFzo{xB9!8U02D zFg=1tXbxkANS)!dJr3lNck?cJ4}E$VEqbU$>$gg0!o8}P=g{N5T+Day(i{d(MFIRp z*qax^z8Ek+QEtG)@nTtKn9c|2Iy)O2l;rnwIOy;k>5m#6zZF^r5FcXVPS4>bZCxlj zQYQn@Tsf{R*T(V4SgSf1Hv*F2AP#X?{HV3ZL%*$l?uh(z3}y8AAcx_u#{j1ox&t{T z(QqzSouT^;9F2201Ro^6e6TpNT%ejd#u_CYF_Q)oYFM0RgoG;5!yJ-Zcxfv94|V^~ zd2C6H&lM1bzon?|x-F}=SvN|DxY1~Y*czPDk&#}P%g#AL`&?bK_ zEHy7U#S^1NkhZQFAy`C@PjU#TTpSwH6nZp4V5euWbtwa93U~}Lj{R%Li{wRP)4wCF(9X+irTq67yeWPz literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/blobstore/__init__.py b/google_appengine/google/appengine/api/blobstore/__init__.py new file mode 100644 index 0000000..0a9f146 --- /dev/null +++ b/google_appengine/google/appengine/api/blobstore/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Blobstore API module.""" + +from blobstore import * diff --git a/google_appengine/google/appengine/api/blobstore/__init__.pyc b/google_appengine/google/appengine/api/blobstore/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4147ddd5d5ca60756494cb8090e69e43a13bff47 GIT binary patch literal 254 zcwW2siI?k?Oo~r30~9a;u>%knivWof28Jjg$;gnx#E{L%5R$^o5Ujxhk`)CKPC5BW z#U=SgsS1t(o(j47DWy57dYS)$K!Xt|T>>Js{4_u&maqef`1q9kzi7CbU>G}ETIjQl91qG>j>6v+{Pyu~}Kw?3r jKHOma`1s7c%#!$cy@JXT4xp(v5Et4Bfvf>Jij4^XM>auH literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/blobstore/blobstore.py b/google_appengine/google/appengine/api/blobstore/blobstore.py new file mode 100755 index 0000000..61d5a4e --- /dev/null +++ b/google_appengine/google/appengine/api/blobstore/blobstore.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A Python blobstore API used by app developers. + +Contains methods uses to interface with Blobstore API. Defines db.Key-like +class representing a blob-key. Contains API part that forward to apiproxy. +""" + + + + +import datetime +import time + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api import api_base_pb +from google.appengine.api.blobstore import blobstore_service_pb +from google.appengine.runtime import apiproxy_errors + + +__all__ = ['BLOB_INFO_KIND', + 'BLOB_KEY_HEADER', + 'BLOB_RANGE_HEADER', + 'MAX_BLOB_FETCH_SIZE', + 'UPLOAD_INFO_CREATION_HEADER', + 'BlobFetchSizeTooLargeError', + 'BlobKey', + 'BlobNotFoundError', + 'DataIndexOutOfRangeError', + 'Error', + 'InternalError', + 'create_upload_url', + 'delete', + 'fetch_data', + ] + + +BlobKey = datastore_types.BlobKey + + +BLOB_INFO_KIND = '__BlobInfo__' + +BLOB_KEY_HEADER = 'X-AppEngine-BlobKey' + +BLOB_RANGE_HEADER = 'X-AppEngine-BlobRange' + +MAX_BLOB_FETCH_SIZE = (1 << 20) - (1 << 15) + +UPLOAD_INFO_CREATION_HEADER = 'X-AppEngine-Upload-Creation' +_BASE_CREATION_HEADER_FORMAT = '%Y-%m-%d %H:%M:%S' + +class Error(Exception): + """Base blobstore error type.""" + + +class InternalError(Error): + """Raised when an internal error occurs within API.""" + + +class BlobNotFoundError(Error): + """Raised when attempting to access blob data for non-existant blob.""" + + +class DataIndexOutOfRangeError(Error): + """Raised when attempting to access indexes out of range in wrong order.""" + + +class BlobFetchSizeTooLargeError(Error): + """Raised when attempting to fetch too large a block from a blob.""" + + +class _CreationFormatError(Error): + """Raised when attempting to parse bad creation date format.""" + + +def _ToBlobstoreError(error): + """Translate an application error to a datastore Error, if possible. + + Args: + error: An ApplicationError to translate. + """ + error_map = { + blobstore_service_pb.BlobstoreServiceError.INTERNAL_ERROR: + InternalError, + blobstore_service_pb.BlobstoreServiceError.BLOB_NOT_FOUND: + BlobNotFoundError, + blobstore_service_pb.BlobstoreServiceError.DATA_INDEX_OUT_OF_RANGE: + DataIndexOutOfRangeError, + blobstore_service_pb.BlobstoreServiceError.BLOB_FETCH_SIZE_TOO_LARGE: + BlobFetchSizeTooLargeError, + } + + if error.application_error in error_map: + return error_map[error.application_error](error.error_detail) + else: + return error + + +def _format_creation(stamp): + """Format an upload creation timestamp with milliseconds. + + This method is necessary to format a timestamp with microseconds on Python + versions before 2.6. + + Cannot simply convert datetime objects to str because the microseconds are + stripped from the format when set to 0. The upload creation date format will + always have microseconds padded out to 6 places. + + Args: + stamp: datetime.datetime object to format. + + Returns: + Formatted datetime as Python 2.6 format '%Y-%m-%d %H:%M:%S.%f'. + """ + return '%s.%06d' % (stamp.strftime(_BASE_CREATION_HEADER_FORMAT), + stamp.microsecond) + + +def _parse_creation(creation_string, field_name): + """Parses upload creation string from header format. + + Parse creation date of the format: + + YYYY-mm-dd HH:MM:SS.ffffff + + Y: Year + m: Month (01-12) + d: Day (01-31) + H: Hour (00-24) + M: Minute (00-59) + S: Second (00-59) + f: Microsecond + + Args: + creation_string: String creation date format. + + Returns: + datetime object parsed from creation_string. + + Raises: + _CreationFormatError when the creation string is formatted incorrectly. + """ + split_creation_string = creation_string.split('.', 1) + if len(split_creation_string) != 2: + raise _CreationFormatError( + 'Could not parse creation %s in field %s.' % (creation_string, + field_name)) + timestamp_string, microsecond = split_creation_string + + try: + timestamp = time.strptime(timestamp_string, + _BASE_CREATION_HEADER_FORMAT) + microsecond = int(microsecond) + except ValueError: + raise _CreationFormatError('Could not parse creation %s in field %s.' + % (creation_string, field_name)) + + return datetime.datetime(*timestamp[:6] + tuple([microsecond])) + + +def create_upload_url(success_path, + _make_sync_call=apiproxy_stub_map.MakeSyncCall): + """Create upload URL for POST form. + + Args: + success_path: Path within application to call when POST is successful + and upload is complete. + _make_sync_call: Used for dependency injection in tests. + """ + request = blobstore_service_pb.CreateUploadURLRequest() + response = blobstore_service_pb.CreateUploadURLResponse() + request.set_success_path(success_path) + try: + _make_sync_call('blobstore', 'CreateUploadURL', request, response) + except apiproxy_errors.ApplicationError, e: + raise _ToBlobstoreError(e) + + return response.url() + + +def delete(blob_keys, _make_sync_call=apiproxy_stub_map.MakeSyncCall): + """Delete a blob from Blobstore. + + Args: + blob_keys: Single instance or list of blob keys. A blob-key can be either + a string or an instance of BlobKey. + _make_sync_call: Used for dependency injection in tests. + """ + if isinstance(blob_keys, (basestring, BlobKey)): + blob_keys = [blob_keys] + request = blobstore_service_pb.DeleteBlobRequest() + for blob_key in blob_keys: + request.add_blob_key(str(blob_key)) + response = api_base_pb.VoidProto() + try: + _make_sync_call('blobstore', 'DeleteBlob', request, response) + except apiproxy_errors.ApplicationError, e: + raise _ToBlobstoreError(e) + + +def fetch_data(blob_key, start_index, end_index, + _make_sync_call=apiproxy_stub_map.MakeSyncCall): + """Fetch data for blob. + + See docstring for ext.blobstore.fetch_data for more details. + + Args: + blob: BlobKey, str or unicode representation of BlobKey of + blob to fetch data from. + start_index: Start index of blob data to fetch. May not be negative. + end_index: End index (exclusive) of blob data to fetch. Must be + >= start_index. + + Returns: + str containing partial data of blob. See docstring for + ext.blobstore.fetch_data for more details. + + Raises: + See docstring for ext.blobstore.fetch_data for more details. + """ + if not isinstance(start_index, (int, long)): + raise TypeError('start_index must be integer.') + + if not isinstance(end_index, (int, long)): + raise TypeError('end_index must be integer.') + + if isinstance(blob_key, BlobKey): + blob_key = str(blob_key).decode('utf-8') + elif isinstance(blob_key, str): + blob_key = blob_key.decode('utf-8') + elif not isinstance(blob_key, unicode): + raise TypeError('Blob-key must be str, unicode or BlobKey: %s' % blob_key) + + if start_index < 0: + raise DataIndexOutOfRangeError( + 'May not fetch blob at negative index.') + + if end_index < start_index: + raise DataIndexOutOfRangeError( + 'Start index %d > end index %d' % (start_index, end_index)) + + fetch_size = end_index - start_index + 1 + + if fetch_size > MAX_BLOB_FETCH_SIZE: + raise BlobFetchSizeTooLargeError( + 'Blob fetch size is too large: %d' % fetch_size) + + request = blobstore_service_pb.FetchDataRequest() + response = blobstore_service_pb.FetchDataResponse() + + request.set_blob_key(blob_key) + request.set_start_index(start_index) + request.set_end_index(end_index) + + try: + _make_sync_call('blobstore', 'FetchData', request, response) + except apiproxy_errors.ApplicationError, e: + raise _ToBlobstoreError(e) + + return response.data() diff --git a/google_appengine/google/appengine/api/blobstore/blobstore.pyc b/google_appengine/google/appengine/api/blobstore/blobstore.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2af40ba9b951681240b5d1cad715e607b477977b GIT binary patch literal 8986 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 Qom`xppL}oT;>@N00o&U9TmS$7 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/blobstore/blobstore_service_pb.py b/google_appengine/google/appengine/api/blobstore/blobstore_service_pb.py new file mode 100755 index 0000000..4bb7f55 --- /dev/null +++ b/google_appengine/google/appengine/api/blobstore/blobstore_service_pb.py @@ -0,0 +1,773 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.api.api_base_pb import * +import google.appengine.api.api_base_pb +class BlobstoreServiceError(ProtocolBuffer.ProtocolMessage): + + OK = 0 + INTERNAL_ERROR = 1 + URL_TOO_LONG = 2 + PERMISSION_DENIED = 3 + BLOB_NOT_FOUND = 4 + DATA_INDEX_OUT_OF_RANGE = 5 + BLOB_FETCH_SIZE_TOO_LARGE = 6 + + _ErrorCode_NAMES = { + 0: "OK", + 1: "INTERNAL_ERROR", + 2: "URL_TOO_LONG", + 3: "PERMISSION_DENIED", + 4: "BLOB_NOT_FOUND", + 5: "DATA_INDEX_OUT_OF_RANGE", + 6: "BLOB_FETCH_SIZE_TOO_LARGE", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateUploadURLRequest(ProtocolBuffer.ProtocolMessage): + has_success_path_ = 0 + success_path_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def success_path(self): return self.success_path_ + + def set_success_path(self, x): + self.has_success_path_ = 1 + self.success_path_ = x + + def clear_success_path(self): + if self.has_success_path_: + self.has_success_path_ = 0 + self.success_path_ = "" + + def has_success_path(self): return self.has_success_path_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_success_path()): self.set_success_path(x.success_path()) + + def Equals(self, x): + if x is self: return 1 + if self.has_success_path_ != x.has_success_path_: return 0 + if self.has_success_path_ and self.success_path_ != x.success_path_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_success_path_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: success_path not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.success_path_)) + return n + 1 + + def Clear(self): + self.clear_success_path() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.success_path_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_success_path(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_success_path_: res+=prefix+("success_path: %s\n" % self.DebugFormatString(self.success_path_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + ksuccess_path = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "success_path", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateUploadURLResponse(ProtocolBuffer.ProtocolMessage): + has_url_ = 0 + url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def url(self): return self.url_ + + def set_url(self, x): + self.has_url_ = 1 + self.url_ = x + + def clear_url(self): + if self.has_url_: + self.has_url_ = 0 + self.url_ = "" + + def has_url(self): return self.has_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_url()): self.set_url(x.url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_url_ != x.has_url_: return 0 + if self.has_url_ and self.url_ != x.url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.url_)) + return n + 1 + + def Clear(self): + self.clear_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_url_: res+=prefix+("url: %s\n" % self.DebugFormatString(self.url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kurl = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DeleteBlobRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.blob_key_ = [] + if contents is not None: self.MergeFromString(contents) + + def blob_key_size(self): return len(self.blob_key_) + def blob_key_list(self): return self.blob_key_ + + def blob_key(self, i): + return self.blob_key_[i] + + def set_blob_key(self, i, x): + self.blob_key_[i] = x + + def add_blob_key(self, x): + self.blob_key_.append(x) + + def clear_blob_key(self): + self.blob_key_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.blob_key_size()): self.add_blob_key(x.blob_key(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.blob_key_) != len(x.blob_key_): return 0 + for e1, e2 in zip(self.blob_key_, x.blob_key_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.blob_key_) + for i in xrange(len(self.blob_key_)): n += self.lengthString(len(self.blob_key_[i])) + return n + 0 + + def Clear(self): + self.clear_blob_key() + + def OutputUnchecked(self, out): + for i in xrange(len(self.blob_key_)): + out.putVarInt32(10) + out.putPrefixedString(self.blob_key_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_blob_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.blob_key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("blob_key%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kblob_key = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "blob_key", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class FetchDataRequest(ProtocolBuffer.ProtocolMessage): + has_blob_key_ = 0 + blob_key_ = "" + has_start_index_ = 0 + start_index_ = 0 + has_end_index_ = 0 + end_index_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def blob_key(self): return self.blob_key_ + + def set_blob_key(self, x): + self.has_blob_key_ = 1 + self.blob_key_ = x + + def clear_blob_key(self): + if self.has_blob_key_: + self.has_blob_key_ = 0 + self.blob_key_ = "" + + def has_blob_key(self): return self.has_blob_key_ + + def start_index(self): return self.start_index_ + + def set_start_index(self, x): + self.has_start_index_ = 1 + self.start_index_ = x + + def clear_start_index(self): + if self.has_start_index_: + self.has_start_index_ = 0 + self.start_index_ = 0 + + def has_start_index(self): return self.has_start_index_ + + def end_index(self): return self.end_index_ + + def set_end_index(self, x): + self.has_end_index_ = 1 + self.end_index_ = x + + def clear_end_index(self): + if self.has_end_index_: + self.has_end_index_ = 0 + self.end_index_ = 0 + + def has_end_index(self): return self.has_end_index_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_blob_key()): self.set_blob_key(x.blob_key()) + if (x.has_start_index()): self.set_start_index(x.start_index()) + if (x.has_end_index()): self.set_end_index(x.end_index()) + + def Equals(self, x): + if x is self: return 1 + if self.has_blob_key_ != x.has_blob_key_: return 0 + if self.has_blob_key_ and self.blob_key_ != x.blob_key_: return 0 + if self.has_start_index_ != x.has_start_index_: return 0 + if self.has_start_index_ and self.start_index_ != x.start_index_: return 0 + if self.has_end_index_ != x.has_end_index_: return 0 + if self.has_end_index_ and self.end_index_ != x.end_index_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_blob_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: blob_key not set.') + if (not self.has_start_index_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: start_index not set.') + if (not self.has_end_index_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: end_index not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.blob_key_)) + n += self.lengthVarInt64(self.start_index_) + n += self.lengthVarInt64(self.end_index_) + return n + 3 + + def Clear(self): + self.clear_blob_key() + self.clear_start_index() + self.clear_end_index() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.blob_key_) + out.putVarInt32(16) + out.putVarInt64(self.start_index_) + out.putVarInt32(24) + out.putVarInt64(self.end_index_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_blob_key(d.getPrefixedString()) + continue + if tt == 16: + self.set_start_index(d.getVarInt64()) + continue + if tt == 24: + self.set_end_index(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_blob_key_: res+=prefix+("blob_key: %s\n" % self.DebugFormatString(self.blob_key_)) + if self.has_start_index_: res+=prefix+("start_index: %s\n" % self.DebugFormatInt64(self.start_index_)) + if self.has_end_index_: res+=prefix+("end_index: %s\n" % self.DebugFormatInt64(self.end_index_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kblob_key = 1 + kstart_index = 2 + kend_index = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "blob_key", + 2: "start_index", + 3: "end_index", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class FetchDataResponse(ProtocolBuffer.ProtocolMessage): + has_data_ = 0 + data_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def data(self): return self.data_ + + def set_data(self, x): + self.has_data_ = 1 + self.data_ = x + + def clear_data(self): + if self.has_data_: + self.has_data_ = 0 + self.data_ = "" + + def has_data(self): return self.has_data_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_data()): self.set_data(x.data()) + + def Equals(self, x): + if x is self: return 1 + if self.has_data_ != x.has_data_: return 0 + if self.has_data_ and self.data_ != x.data_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_data_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: data not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.data_)) + return n + 2 + + def Clear(self): + self.clear_data() + + def OutputUnchecked(self, out): + out.putVarInt32(8002) + out.putPrefixedString(self.data_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8002: + self.set_data(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_data_: res+=prefix+("data: %s\n" % self.DebugFormatString(self.data_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdata = 1000 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1000: "data", + }, 1000) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1000: ProtocolBuffer.Encoder.STRING, + }, 1000, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DecodeBlobKeyRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.blob_key_ = [] + if contents is not None: self.MergeFromString(contents) + + def blob_key_size(self): return len(self.blob_key_) + def blob_key_list(self): return self.blob_key_ + + def blob_key(self, i): + return self.blob_key_[i] + + def set_blob_key(self, i, x): + self.blob_key_[i] = x + + def add_blob_key(self, x): + self.blob_key_.append(x) + + def clear_blob_key(self): + self.blob_key_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.blob_key_size()): self.add_blob_key(x.blob_key(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.blob_key_) != len(x.blob_key_): return 0 + for e1, e2 in zip(self.blob_key_, x.blob_key_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.blob_key_) + for i in xrange(len(self.blob_key_)): n += self.lengthString(len(self.blob_key_[i])) + return n + 0 + + def Clear(self): + self.clear_blob_key() + + def OutputUnchecked(self, out): + for i in xrange(len(self.blob_key_)): + out.putVarInt32(10) + out.putPrefixedString(self.blob_key_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_blob_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.blob_key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("blob_key%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kblob_key = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "blob_key", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DecodeBlobKeyResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.decoded_ = [] + if contents is not None: self.MergeFromString(contents) + + def decoded_size(self): return len(self.decoded_) + def decoded_list(self): return self.decoded_ + + def decoded(self, i): + return self.decoded_[i] + + def set_decoded(self, i, x): + self.decoded_[i] = x + + def add_decoded(self, x): + self.decoded_.append(x) + + def clear_decoded(self): + self.decoded_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.decoded_size()): self.add_decoded(x.decoded(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.decoded_) != len(x.decoded_): return 0 + for e1, e2 in zip(self.decoded_, x.decoded_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.decoded_) + for i in xrange(len(self.decoded_)): n += self.lengthString(len(self.decoded_[i])) + return n + 0 + + def Clear(self): + self.clear_decoded() + + def OutputUnchecked(self, out): + for i in xrange(len(self.decoded_)): + out.putVarInt32(10) + out.putPrefixedString(self.decoded_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_decoded(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.decoded_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("decoded%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdecoded = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "decoded", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['BlobstoreServiceError','CreateUploadURLRequest','CreateUploadURLResponse','DeleteBlobRequest','FetchDataRequest','FetchDataResponse','DecodeBlobKeyRequest','DecodeBlobKeyResponse'] diff --git a/google_appengine/google/appengine/api/blobstore/blobstore_service_pb.pyc b/google_appengine/google/appengine/api/blobstore/blobstore_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf4406cb6af86608aa72fe4ee7c2557165dbacab GIT binary patch literal 38346 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/blobstore/blobstore_stub.py b/google_appengine/google/appengine/api/blobstore/blobstore_stub.py new file mode 100755 index 0000000..032757c --- /dev/null +++ b/google_appengine/google/appengine/api/blobstore/blobstore_stub.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Datastore backed Blobstore API stub. + +Class: + BlobstoreServiceStub: BlobstoreService stub backed by datastore. +""" + + + + + + +import os +import time + +from google.appengine.api import apiproxy_stub +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api import users +from google.appengine.api import blobstore +from google.appengine.api.blobstore import blobstore_service_pb +from google.appengine.runtime import apiproxy_errors + + +__all__ = ['BlobStorage', + 'BlobstoreServiceStub', + 'ConfigurationError', + 'CreateUploadSession', + 'Error', + ] + + +class Error(Exception): + """Base blobstore error type.""" + + +class ConfigurationError(Error): + """Raised when environment is not correctly configured.""" + + +_UPLOAD_SESSION_KIND = '__BlobUploadSession__' + + +def CreateUploadSession(creation, success_path, user): + """Create upload session in datastore. + + Creates an upload session and puts it in Datastore to be referenced by + upload handler later. + + Args: + creation: Creation timestamp. + success_path: Path in users application to call upon success. + user: User that initiated this upload, if any. + + Returns: + String encoded key of new Datastore entity. + """ + entity = datastore.Entity(_UPLOAD_SESSION_KIND, namespace='') + entity.update({'creation': creation, + 'success_path': success_path, + 'user': user, + 'state': 'init'}) + datastore.Put(entity) + return str(entity.key()) + + +class BlobStorage(object): + """Base class for defining how blobs are stored. + + This base class merely defines an interface that all stub blob-storage + mechanisms must implement. + """ + + def StoreBlob(self, blob_key, blob_stream): + """Store blob stream. + + Implement this method to persist blob data. + + Args: + blob_key: Blob key of blob to store. + blob_stream: Stream or stream-like object that will generate blob content. + """ + raise NotImplementedError('Storage class must override StoreBlob method.') + + def OpenBlob(self, blob_key): + """Open blob for streaming. + + Args: + blob_key: Blob-key of existing blob to open for reading. + + Returns: + Open file stream for reading blob. Caller is responsible for closing + file. + """ + raise NotImplementedError('Storage class must override OpenBlob method.') + + def DeleteBlob(self, blob_key): + """Delete blob data from storage. + + Args: + blob_key: Blob-key of existing blob to delete. + """ + raise NotImplementedError('Storage class must override DeleteBlob method.') + + +class BlobstoreServiceStub(apiproxy_stub.APIProxyStub): + """Datastore backed Blobstore service stub. + + This stub stores manages upload sessions in the Datastore and must be + provided with a blob_storage object to know where the actual blob + records can be found after having been uploaded. + + This stub does not handle the actual creation of blobs, neither the BlobInfo + in the Datastore nor creation of blob data in the blob_storage. It does, + however, assume that another part of the system has created these and + uses these objects for deletion. + + An upload session is created when the CreateUploadURL request is handled and + put in the Datastore under the __BlobUploadSession__ kind. There is no + analog for this kind on a production server. Other than creation, this stub + not work with session objects. The URLs created by this service stub are: + + http://:// + + This is very similar to what the URL is on a production server. The session + info is the string encoded version of the session entity + """ + + def __init__(self, + blob_storage, + time_function=time.time, + service_name='blobstore', + uploader_path='_ah/upload/'): + """Constructor. + + Args: + blob_storage: BlobStorage class instance used for blob storage. + time_function: Used for dependency injection in tests. + service_name: Service name expected for all calls. + uploader_path: Path to upload handler pointed to by URLs generated + by this service stub. + """ + super(BlobstoreServiceStub, self).__init__(service_name) + self.__storage = blob_storage + self.__time_function = time_function + self.__next_session_id = 1 + self.__uploader_path = uploader_path + + @property + def storage(self): + """Access BlobStorage used by service stub. + + Returns: + BlobStorage instance used by blobstore service stub. + """ + return self.__storage + + def _GetEnviron(self, name): + """Helper method ensures environment configured as expected. + + Args: + name: Name of environment variable to get. + + Returns: + Environment variable associated with name. + + Raises: + ConfigurationError if required environment variable is not found. + """ + try: + return os.environ[name] + except KeyError: + raise ConfigurationError('%s is not set in environment.' % name) + + def _CreateSession(self, success_path, user): + """Create new upload session. + + Args: + success_path: Application path to call upon successful POST. + user: User that initiated the upload session. + + Returns: + String encoded key of a new upload session created in the datastore. + """ + return CreateUploadSession(self.__time_function(), + success_path, + user) + + def _Dynamic_CreateUploadURL(self, request, response): + """Create upload URL implementation. + + Create a new upload session. The upload session key is encoded in the + resulting POST URL. This URL is embedded in a POST form by the application + which contacts the uploader when the user posts. + + Args: + request: A fully initialized CreateUploadURLRequest instance. + response: A CreateUploadURLResponse instance. + """ + session = self._CreateSession(request.success_path(), + users.get_current_user()) + + response.set_url('http://%s:%s/%s%s' % (self._GetEnviron('SERVER_NAME'), + self._GetEnviron('SERVER_PORT'), + self.__uploader_path, + session)) + + def _Dynamic_DeleteBlob(self, request, response): + """Delete a blob by its blob-key. + + Delete a blob from the blobstore using its blob-key. Deleting blobs that + do not exist is a no-op. + + Args: + request: A fully initialized DeleteBlobRequest instance. + response: Not used but should be a VoidProto. + """ + for blob_key in request.blob_key_list(): + key = datastore_types.Key.from_path(blobstore.BLOB_INFO_KIND, + str(blob_key), + namespace='') + + datastore.Delete(key) + self.__storage.DeleteBlob(blob_key) + + def _Dynamic_FetchData(self, request, response): + """Fetch a blob fragment from a blob by its blob-key. + + Fetches a blob fragment using its blob-key. Start index is inclusive, + end index is inclusive. Valid requests for information outside of + the range of the blob return a partial string or empty string if entirely + out of range. + + Args: + request: A fully initialized FetchDataRequest instance. + response: A FetchDataResponse instance. + + Raises: + ApplicationError when application has the following errors: + INDEX_OUT_OF_RANGE: Index is negative or end > start. + BLOB_FETCH_SIZE_TOO_LARGE: Request blob fragment is larger than + MAX_BLOB_FRAGMENT_SIZE. + BLOB_NOT_FOUND: If invalid blob-key is provided or is not found. + """ + start_index = request.start_index() + if start_index < 0: + raise apiproxy_errors.ApplicationError( + blobstore_service_pb.BlobstoreServiceError.DATA_INDEX_OUT_OF_RANGE) + + end_index = request.end_index() + if end_index < start_index: + raise apiproxy_errors.ApplicationError( + blobstore_service_pb.BlobstoreServiceError.DATA_INDEX_OUT_OF_RANGE) + + fetch_size = end_index - start_index + 1 + if fetch_size > blobstore.MAX_BLOB_FETCH_SIZE: + raise apiproxy_errors.ApplicationError( + blobstore_service_pb.BlobstoreServiceError.BLOB_FETCH_SIZE_TOO_LARGE) + + blob_key = request.blob_key() + blob_info_key = datastore.Key.from_path(blobstore.BLOB_INFO_KIND, + blob_key, + namespace='') + try: + datastore.Get(blob_info_key) + except datastore_errors.EntityNotFoundError, err: + raise apiproxy_errors.ApplicationError( + blobstore_service_pb.BlobstoreServiceError.BLOB_NOT_FOUND) + + blob_file = self.__storage.OpenBlob(blob_key) + blob_file.seek(start_index) + response.set_data(blob_file.read(fetch_size)) diff --git a/google_appengine/google/appengine/api/blobstore/blobstore_stub.pyc b/google_appengine/google/appengine/api/blobstore/blobstore_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf963521cb017df547610cfaeb70dff7554e5c24 GIT binary patch literal 10890 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 Bs^0(r literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/blobstore/file_blob_storage.py b/google_appengine/google/appengine/api/blobstore/file_blob_storage.py new file mode 100755 index 0000000..9b9dbc9 --- /dev/null +++ b/google_appengine/google/appengine/api/blobstore/file_blob_storage.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Implementation of Blobstore stub storage based on file system. + +Contains implementation of blobstore_stub.BlobStorage that writes +blobs directly to a filesystem. +""" + + + + + + +import errno +import os + +from google.appengine.api import blobstore +from google.appengine.api.blobstore import blobstore_stub + + +__all__ = ['FileBlobStorage'] + + +import __builtin__ +_local_open = __builtin__.open + + +class FileBlobStorage(blobstore_stub.BlobStorage): + """Storage mechanism for storing blob data on local disk.""" + + def __init__(self, storage_directory, app_id): + """Constructor. + + Args: + storage_directory: Directory within which to store blobs. + app_id: App id to store blobs on behalf of. + """ + self._storage_directory = storage_directory + self._app_id = app_id + + @classmethod + def _BlobKey(cls, blob_key): + """Normalize to instance of BlobKey.""" + if not isinstance(blob_key, blobstore.BlobKey): + return blobstore.BlobKey(unicode(blob_key)) + return blob_key + + def _DirectoryForBlob(self, blob_key): + """Determine which directory where a blob is stored. + + Each blob gets written to a directory underneath the storage objects + storage directory based on the blobs kind, app-id and first character of + its name. So blobs with blob-keys: + + _ACFDEDG + _MNOPQRS + _RSTUVWX + + Are stored in: + + /blob/myapp/A + /blob/myapp/M + /R + + Args: + blob_key: Blob key to determine directory for. + + Returns: + Directory relative to this objects storage directory to + where blob is stored or should be stored. + """ + blob_key = self._BlobKey(blob_key) + return os.path.join(self._storage_directory, + self._app_id, + str(blob_key)[1]) + + def _FileForBlob(self, blob_key): + """Calculate full filename to store blob contents in. + + This method does not check to see if the file actually exists. + + Args: + blob_key: Blob key of blob to calculate file for. + + Returns: + Complete path for file used for storing blob. + """ + blob_key = self._BlobKey(blob_key) + return os.path.join(self._DirectoryForBlob(blob_key), str(blob_key)[1:]) + + def StoreBlob(self, blob_key, blob_stream): + """Store blob stream to disk. + + Args: + blob_key: Blob key of blob to store. + blob_stream: Stream or stream-like object that will generate blob content. + """ + blob_key = self._BlobKey(blob_key) + blob_directory = self._DirectoryForBlob(blob_key) + if not os.path.exists(blob_directory): + os.makedirs(blob_directory) + blob_file = self._FileForBlob(blob_key) + output = _local_open(blob_file, 'wb') + + try: + while True: + block = blob_stream.read(1 << 20) + if not block: + break + output.write(block) + finally: + output.close() + + def OpenBlob(self, blob_key): + """Open blob file for streaming. + + Args: + blob_key: Blob-key of existing blob to open for reading. + + Returns: + Open file stream for reading blob from disk. + """ + return _local_open(self._FileForBlob(blob_key), 'rb') + + def DeleteBlob(self, blob_key): + """Delete blob data from disk. + + Deleting an unknown blob will not raise an error. + + Args: + blob_key: Blob-key of existing blob to delete. + """ + try: + os.remove(self._FileForBlob(blob_key)) + except OSError, e: + if e.errno != errno.ENOENT: + raise e diff --git a/google_appengine/google/appengine/api/blobstore/file_blob_storage.pyc b/google_appengine/google/appengine/api/blobstore/file_blob_storage.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e41dfe5994b16454320eaf9cf982ce0028af466e GIT binary patch literal 4872 zcwWU=-*4l_5nht|VaZPH>m9CXkfux!q(U1@K+zV38>GG1zM^Pir_?o<2n7g=TuEzD zT!mc4QE>Orx{pO)``(AVK&r8_%F^Wqq%#qM%;cJ`ZZW@qo;|9PSN z)ho$+u_*s`==WRt6>Dk|Ax6|XB5}ps5hJJSyJF;W-xG6Bqzy4@2nXYam^Ve*5~CK4 zn_^0f+SQ^RG1?JwN2rb%bp?HuEunVBgub@K--Ngl;^WaCM2EdLO!yZyzh5jhdl!cvQX>UpE=sfsqmd_M0}uf(0=EelV7>+H(P;x&baqNuM;(#0xziQF zum=Yhzoe!*>q5q}C{xA4pXi+7RN0il@sr3zfHc){loABRydQhC)GG}=hOYb6JaNPm zAwuf7++m^&dypyq0YRM=CSS#*oP6Tb--CQw9C6RDTo_u6b-q6GPpYnesmx4e{^d-? zGk|Yh&xrQR_0e(}s^rLjuw42oc}@uSkL4^%C**Ux;*1S5-=ikHwzmQGLaVZegPI0T zA=8PugU&Vi7R{F8a3D7Yg^d`@^g<4@IFIz;L|Z|s63u;aN88b z0V_bP|G|}4G5m>PDMs$Hu-E+ ztg=MrnT!lY#!S|!P>(N2py+PJ*4CmrQDTV=k-5r}w-82$6h2XwP=3n`<5OP9qnPX` zud^~mrex7V_I-b-%e9D4?jDjKDWuDo3?CdnJUKo2T{V3F?EHg22E%F?3?KdJ!^a<0 zxj>euC!Wn*`p!Q6RXUcq;lGF{gd%0pX-}*#@*powx#N zR(V#d*d(f4rgXJ@%HcvOtEk*?gA0rXldr}Aw~7JD zbiw&)NgUwLg;rUB5DOYruu_r- z$ZLFy$m4m_+(h1wuG}i}1R-@CrSXbfBK^rKP5H_Ocb~l;{Fv$kDhjAR$Vvx4g6|g6 z%yi->S`y}2%Ak}6w2ZQ!bU(g1mgJ5QV9^Iwb4 zelF;e&~K5bi&B-UDTvaVmRL50^8ssV3f097)P6D%&f~{aeqnYOWvufNjfRYU@rAIZ zR>?OCMj0(Q&7hq6wsc`Q``e;c;gLVIikS-N9j0ntT?J)5t0)YnGLt#zw#|O4kn_pq zn0ce_QFBIEABf&wO`*Hb@Q5oPOMAXn7tvf&jw=GJHM{UG7mlGOQgCv5l&>Ur$iT#a za$I9`FHUtKZE~x*$V#?OSr^k=+rF_ZVmjB;#7jBUMOzmU({T)9U|p zeZrh`2RF4NL2O^V4UZ}@k*t{C>kTZBa?8b^H=y{XpLrzZxNNot accepting submissions right now: %s

' % + datastore_readonly.admin_message()) + # ...render form with form elements disabled... + else: + # ...render form normally... + + Individual API wrapper modules should expose CapabilitySet objects + for users rather than relying on users to create them. They may + also create convenience methods (e.g. db.IsReadOnly()) that delegate + to the relevant CapabilitySet. + +Classes defined here: + CapabilitySet: encapsulates one or more capabilities, allows introspection. + UnknownCapabilityError: thrown when an unknown capability is requested. +""" + + + + + +from google.appengine.api.capabilities import capability_service_pb +from google.appengine.base import capabilities_pb +from google.appengine.api import apiproxy_stub_map + + +IsEnabledRequest = capability_service_pb.IsEnabledRequest +IsEnabledResponse = capability_service_pb.IsEnabledResponse +CapabilityConfig = capabilities_pb.CapabilityConfig + + +class UnknownCapabilityError(Exception): + """An unknown capability was requested.""" + + +class CapabilitySet(object): + """Encapsulates one or more capabilities. + + Capabilities can either be named explicitly, or inferred from the + list of methods provided. If no capabilities or methods are + provided, this will check whether the entire package is enabled. + """ + def __init__(self, package, capabilities=None, methods=None, + stub_map=apiproxy_stub_map): + """Constructor. + + Args: + capabilities: list of strings + methods: list of strings + """ + if capabilities is None: + capabilities = [] + if methods is None: + methods = [] + self._package = package + self._capabilities = ['*'] + capabilities + self._methods = methods + self._stub_map = stub_map + + def is_enabled(self): + """Tests whether the capabilities is currently enabled. + + Returns: + True if API calls that require these capabillities will succeed. + + Raises: + UnknownCapabilityError, if a specified capability was not recognized. + """ + config = self._get_status() + return config.summary_status() in (IsEnabledResponse.ENABLED, + IsEnabledResponse.SCHEDULED_FUTURE, + IsEnabledResponse.SCHEDULED_NOW) + + def will_remain_enabled_for(self, time=60): + """Returns true if it will remain enabled for the specified amount of time. + + Args: + time: Number of seconds in the future to look when checking for scheduled + downtime. + + Returns: + True if there is no scheduled downtime for the specified capability + within the amount of time specified. + + Raises: + UnknownCapabilityError, if a specified capability was not recognized. + """ + config = self._get_status() + + status = config.summary_status() + if status == IsEnabledResponse.ENABLED: + return True + elif status == IsEnabledResponse.SCHEDULED_NOW: + return False + elif status == IsEnabledResponse.SCHEDULED_FUTURE: + if config.has_time_until_scheduled(): + return config.time_until_scheduled() >= time + else: + return True + elif status == IsEnabledResponse.DISABLED: + return False + else: + return False + + def admin_message(self): + """Get any administrator notice messages for these capabilities. + + Returns: + A string containing one or more admin messages, or an empty string. + + Raises: + UnknownCapabilityError, if a specified capability was not recognized. + """ + message_list = [] + for config in self._get_status().config_list(): + message = config.admin_message() + if message and message not in message_list: + message_list.append(message) + return ' '.join(message_list) + + def _get_status(self): + """Get an IsEnabledResponse for the capabilities listed. + + Returns: + IsEnabledResponse for the specified capabilities. + + Raises: + UnknownCapabilityError: If an unknown capability was requested. + """ + req = IsEnabledRequest() + req.set_package(self._package) + for capability in self._capabilities: + req.add_capability(capability) + for method in self._methods: + req.add_call(method) + + resp = capability_service_pb.IsEnabledResponse() + self._stub_map.MakeSyncCall('capability_service', 'IsEnabled', req, resp) + + if resp.summary_status() == IsEnabledResponse.UNKNOWN: + raise UnknownCapabilityError() + + return resp diff --git a/google_appengine/google/appengine/api/capabilities/__init__.pyc b/google_appengine/google/appengine/api/capabilities/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d61b674e7b9b0665928e841673f49c53623e0d96 GIT binary patch literal 5968 zcwW6&-EJGl6`mz2N~9%6t{taNi*y3l6;-HIffgws*hUpej)gc%KuR#8!dNW1Lu#b` zp?8*&S=bk?jlMy1)%(6oZ;C!dZ;SQ;+V7m%A5x}^+*ks3#GN^F=FFMzJLk;){ogCi zzx~*M(ihoZ9nWv^m_MNLg*Zm*2)`oE91&E+aYZ=vUKM9m5!A$S&A!*f*@6h_; zNE&M=>iJliv^`X5YfXp4wRIN0EB6O7JyDW{4cn%T97o={rvtASs113c)`CEuj!i1f znI5rtqKy7M>?9p}Aa}fx*VBPcCta(bl&!6|wQ*HMTKraK-5mR@bla+I%?@wK&wfph zwbs!7FG;L7Gv{*}cW*)NMt-_1|Ki~tAK#`(9_?vEZG#HsZ9 zeKksTG?ZrC3$-zfm_!dxQW?b;Tk@W{zd3riChuj+=KamF$n!%TxuG(K5VF2rn9^>; z5OS6rz*F)E1$U^HDMAOb|g zm@sanIf=)C4``2Kqh?P*#=TS3PYsknu^bzfzz-hqmH>2K1k?o+@>3jTgP>I(`%M*r zGHgqEc%mi}M*$UHV2aUx9G$C3tEjJJsM3?zH?pPL!?yH$?S0dM%f5!o;3Qfo1)c&m z#6+mU5*F}*I`^V<)?#dUC-98Hz5qcTfx|GCT{R=OC9$+OGUEVB4eVDkCYK~?25B2G znq!BK(j+z`I0IOqBRGoA0EMVjx|bwzvIWx;bjynq6(N>@w=7mFoe-Nd?&CO{`oH*V zIS5thhiFPU7ydlgedUgNcKCzoVc1p-+(6@v^eBm6U{*TrxuG{oZ=-jf^F3zh4pO@6T$!|Niy`f=ZNTeNi9 zm_sz1Cy1__Q9tqG&0Q=-@x*Kn<9LYK1lMSYv&=d+%MNUNbJ{1HE@-Y(*KLm`9Wv?x ztx!QzchVL7n*l-Lh+6K5FD^L(*@Y5Jjq=!=syJStY=$(0RE0E#%=N~hWQEj3ol9A2 zLb8Gf$sOijX!gDj(wM0`QymzMq|(f_o|5ENCMi;wPJ_t?Y0=R@B?}6<`7KZR_g?RLaVAjTob4;`BP z5xrLgfS7B0qBm!_1vt$PQ;}(+0idEG_j=He#0KYL>+;-FYxq!r01n5Q5(? zl`?AE_s6u-Dy_G);xrw48qO-7zLTAKEkE}*ox2d4>#|okMn`Ceey6DJs@&GNT@Zt+ zIIW8;?)($YA+~I0KsN(iK(0Rq_9Jki1RtMKN2TK=D$e{c87nFgDEIXd&W+6|6uk(; z&}<6bnQ}(G8KVp+m3rE!Lg)OzxH#AwjU28)K$Js8Eh zByoMYvW(!pkUcBTjmi@Ju5d7~iK$@jp&4=*fAM8WNlV7vXTQnGSt-dGf5IFhS}j_p zImA|Uy+(?Eeutl@AQQ3xhF%w}`VX2faQ*P2iR4QYGBK_lfE`X+^Ob?`P$q|>nNv)? zh3@SvlhfrRg-7hgr7XvIr9w3qSX6?ABcROAe9w^OLKP{25Xr#hykH}=OVtQR^O;*D zV~PRcGuTdJTysnOQ4x_zvI2|4#j^JbfY`YQ#eWjnHA6J&&P`O~%g!z5Lt8S?b{jd$ z{z?YU)(eES3Pa|_C;UvGr*!FFOBGU{$`Ao{LC1Q@YSWysKDw zw~9GPnnL>bK;#zH|MRb;v$r{0=>9;=yNz=BS{T`4kgbZD%7oQ0a#5e;RdI*1e}_7` zw()$wERiQ2f+>ek+aClSLW+&p1bN2sSKgWGPNMz}jZ;Kt93LHgiK6C!FMF0aY!kg= zM_hE23o%=44b5z!uRmA80OIm@B9FNZDwmt!zzy3X0^h0Jc2+6}{K>Mz^yRIPxgGL& zhl{qG)clB=yVMY`SoM|NsLA2mB4cvBUPsxsyxO?F{FBD&0b_wJPCrFs6}Jn;_@&#^ zius87o5x$CJrBv4KW*H4hbHTf4UvI_Ay~qFha)CKb?*g#%8Mfg2%o?Grp0@?D#tOl c@7s?d4}T%D`%}HX3Lvaj8@M|)s<&(Z18;0)9smFU literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/capabilities/capability_service_pb.py b/google_appengine/google/appengine/api/capabilities/capability_service_pb.py new file mode 100755 index 0000000..0abf4f4 --- /dev/null +++ b/google_appengine/google/appengine/api/capabilities/capability_service_pb.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.base.capabilities_pb import * +import google.appengine.base.capabilities_pb +class IsEnabledRequest(ProtocolBuffer.ProtocolMessage): + has_package_ = 0 + package_ = "" + + def __init__(self, contents=None): + self.capability_ = [] + self.call_ = [] + if contents is not None: self.MergeFromString(contents) + + def package(self): return self.package_ + + def set_package(self, x): + self.has_package_ = 1 + self.package_ = x + + def clear_package(self): + if self.has_package_: + self.has_package_ = 0 + self.package_ = "" + + def has_package(self): return self.has_package_ + + def capability_size(self): return len(self.capability_) + def capability_list(self): return self.capability_ + + def capability(self, i): + return self.capability_[i] + + def set_capability(self, i, x): + self.capability_[i] = x + + def add_capability(self, x): + self.capability_.append(x) + + def clear_capability(self): + self.capability_ = [] + + def call_size(self): return len(self.call_) + def call_list(self): return self.call_ + + def call(self, i): + return self.call_[i] + + def set_call(self, i, x): + self.call_[i] = x + + def add_call(self, x): + self.call_.append(x) + + def clear_call(self): + self.call_ = [] + + + def MergeFrom(self, x): + assert x is not self + if (x.has_package()): self.set_package(x.package()) + for i in xrange(x.capability_size()): self.add_capability(x.capability(i)) + for i in xrange(x.call_size()): self.add_call(x.call(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_package_ != x.has_package_: return 0 + if self.has_package_ and self.package_ != x.package_: return 0 + if len(self.capability_) != len(x.capability_): return 0 + for e1, e2 in zip(self.capability_, x.capability_): + if e1 != e2: return 0 + if len(self.call_) != len(x.call_): return 0 + for e1, e2 in zip(self.call_, x.call_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_package_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: package not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.package_)) + n += 1 * len(self.capability_) + for i in xrange(len(self.capability_)): n += self.lengthString(len(self.capability_[i])) + n += 1 * len(self.call_) + for i in xrange(len(self.call_)): n += self.lengthString(len(self.call_[i])) + return n + 1 + + def Clear(self): + self.clear_package() + self.clear_capability() + self.clear_call() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.package_) + for i in xrange(len(self.capability_)): + out.putVarInt32(18) + out.putPrefixedString(self.capability_[i]) + for i in xrange(len(self.call_)): + out.putVarInt32(26) + out.putPrefixedString(self.call_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_package(d.getPrefixedString()) + continue + if tt == 18: + self.add_capability(d.getPrefixedString()) + continue + if tt == 26: + self.add_call(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_package_: res+=prefix+("package: %s\n" % self.DebugFormatString(self.package_)) + cnt=0 + for e in self.capability_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("capability%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + cnt=0 + for e in self.call_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("call%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kpackage = 1 + kcapability = 2 + kcall = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "package", + 2: "capability", + 3: "call", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class IsEnabledResponse(ProtocolBuffer.ProtocolMessage): + + ENABLED = 1 + SCHEDULED_FUTURE = 2 + SCHEDULED_NOW = 3 + DISABLED = 4 + UNKNOWN = 5 + + _SummaryStatus_NAMES = { + 1: "ENABLED", + 2: "SCHEDULED_FUTURE", + 3: "SCHEDULED_NOW", + 4: "DISABLED", + 5: "UNKNOWN", + } + + def SummaryStatus_Name(cls, x): return cls._SummaryStatus_NAMES.get(x, "") + SummaryStatus_Name = classmethod(SummaryStatus_Name) + + has_summary_status_ = 0 + summary_status_ = 0 + has_time_until_scheduled_ = 0 + time_until_scheduled_ = 0 + + def __init__(self, contents=None): + self.config_ = [] + if contents is not None: self.MergeFromString(contents) + + def summary_status(self): return self.summary_status_ + + def set_summary_status(self, x): + self.has_summary_status_ = 1 + self.summary_status_ = x + + def clear_summary_status(self): + if self.has_summary_status_: + self.has_summary_status_ = 0 + self.summary_status_ = 0 + + def has_summary_status(self): return self.has_summary_status_ + + def time_until_scheduled(self): return self.time_until_scheduled_ + + def set_time_until_scheduled(self, x): + self.has_time_until_scheduled_ = 1 + self.time_until_scheduled_ = x + + def clear_time_until_scheduled(self): + if self.has_time_until_scheduled_: + self.has_time_until_scheduled_ = 0 + self.time_until_scheduled_ = 0 + + def has_time_until_scheduled(self): return self.has_time_until_scheduled_ + + def config_size(self): return len(self.config_) + def config_list(self): return self.config_ + + def config(self, i): + return self.config_[i] + + def mutable_config(self, i): + return self.config_[i] + + def add_config(self): + x = CapabilityConfig() + self.config_.append(x) + return x + + def clear_config(self): + self.config_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_summary_status()): self.set_summary_status(x.summary_status()) + if (x.has_time_until_scheduled()): self.set_time_until_scheduled(x.time_until_scheduled()) + for i in xrange(x.config_size()): self.add_config().CopyFrom(x.config(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_summary_status_ != x.has_summary_status_: return 0 + if self.has_summary_status_ and self.summary_status_ != x.summary_status_: return 0 + if self.has_time_until_scheduled_ != x.has_time_until_scheduled_: return 0 + if self.has_time_until_scheduled_ and self.time_until_scheduled_ != x.time_until_scheduled_: return 0 + if len(self.config_) != len(x.config_): return 0 + for e1, e2 in zip(self.config_, x.config_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_summary_status_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: summary_status not set.') + for p in self.config_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.summary_status_) + if (self.has_time_until_scheduled_): n += 1 + self.lengthVarInt64(self.time_until_scheduled_) + n += 1 * len(self.config_) + for i in xrange(len(self.config_)): n += self.lengthString(self.config_[i].ByteSize()) + return n + 1 + + def Clear(self): + self.clear_summary_status() + self.clear_time_until_scheduled() + self.clear_config() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.summary_status_) + if (self.has_time_until_scheduled_): + out.putVarInt32(16) + out.putVarInt64(self.time_until_scheduled_) + for i in xrange(len(self.config_)): + out.putVarInt32(26) + out.putVarInt32(self.config_[i].ByteSize()) + self.config_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_summary_status(d.getVarInt32()) + continue + if tt == 16: + self.set_time_until_scheduled(d.getVarInt64()) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_config().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_summary_status_: res+=prefix+("summary_status: %s\n" % self.DebugFormatInt32(self.summary_status_)) + if self.has_time_until_scheduled_: res+=prefix+("time_until_scheduled: %s\n" % self.DebugFormatInt64(self.time_until_scheduled_)) + cnt=0 + for e in self.config_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("config%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + ksummary_status = 1 + ktime_until_scheduled = 2 + kconfig = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "summary_status", + 2: "time_until_scheduled", + 3: "config", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['IsEnabledRequest','IsEnabledResponse'] diff --git a/google_appengine/google/appengine/api/capabilities/capability_service_pb.pyc b/google_appengine/google/appengine/api/capabilities/capability_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0154362ecd4c50d7159c2d8a4ca8983e86b9f1d0 GIT binary patch literal 18156 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/channel/channel.py b/google_appengine/google/appengine/api/channel/channel.py new file mode 100755 index 0000000..805db7c --- /dev/null +++ b/google_appengine/google/appengine/api/channel/channel.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Channel API. + +This module allows App Engine apps to push messages to a client. + +Functions defined in this module: + create_channel: Creates a channel to send messages to. + send_message: Send a message to any clients listening on the given channel. +""" + + +import os + +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_stub_map +from google.appengine.api.channel import channel_service_pb +from google.appengine.runtime import apiproxy_errors + +MAX_DURATION = 60 * 60 * 4 + +MAX_SIMULTANEOUS_CONNECTIONS = 10 + + +class Error(Exception): + """Base error class for this module.""" + + +class InvalidChannelKeyError(Error): + """Error that indicates a bad channel id.""" + +class InvalidChannelKeyError(Error): + """Error that indicates a bad channel key.""" + +class InvalidMessageError(Error): + """Error that indicates a message is malformed.""" + + +class ChannelTimeoutError(Error): + """Error that indicates the given channel has timed out.""" + + +def _ToChannelError(error): + """Translate an application error to a channel Error, if possible. + + Args: + error: An ApplicationError to translate. + + Returns: + The appropriate channel service error, if a match is found, or the original + ApplicationError. + """ + error_map = { + channel_service_pb.ChannelServiceError.INVALID_CHANNEL_KEY: + InvalidChannelKeyError, + channel_service_pb.ChannelServiceError.BAD_MESSAGE: + InvalidMessageError, + channel_service_pb.ChannelServiceError.CHANNEL_TIMEOUT: + ChannelTimeoutError + } + + if error.application_error in error_map: + return error_map[error.application_error](error.error_detail) + else: + return error + + +def _GetService(): + """Gets the service name to use, based on if we're on the dev server.""" + if os.environ.get('SERVER_SOFTWARE', '').startswith('Devel'): + return 'channel' + else: + return 'xmpp' + + +def create_channel(application_key): + """Create a channel. + + Args: + application_key: A key to identify this channel on the server side. + + Returns: + A string id that the client can use to connect to the channel. + + Raises: + InvalidChannelTimeoutError: if the specified timeout is invalid. + Other errors returned by _ToChannelError + """ + + request = channel_service_pb.CreateChannelRequest() + response = channel_service_pb.CreateChannelResponse() + + request.set_application_key(application_key) + + try: + apiproxy_stub_map.MakeSyncCall(_GetService(), + 'CreateChannel', + request, + response) + except apiproxy_errors.ApplicationError, e: + raise _ToChannelError(e) + + return response.client_id() + + +def send_message(application_key, message): + """Send a message to a channel. + + Args: + application_key: The key passed to create_channel. + message: A string representing the message to send. + + Raises: + Errors returned by _ToChannelError + """ + request = channel_service_pb.SendMessageRequest() + response = api_base_pb.VoidProto() + + request.set_application_key(application_key) + request.set_message(message) + + try: + apiproxy_stub_map.MakeSyncCall(_GetService(), + 'SendChannelMessage', + request, + response) + except apiproxy_errors.ApplicationError, e: + raise _ToChannelError(e) diff --git a/google_appengine/google/appengine/api/channel/channel.pyc b/google_appengine/google/appengine/api/channel/channel.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bebc54f9d3362f85fe59996f5347ef15942288c1 GIT binary patch literal 4791 zcwWU=OK;mo5MENUVkvUsB#n^(eQe(Om`t$Fl)*o-j_akQi7UBOk z{HuR}#EgxAIxKeCfx|`)wQKCaWl4>VYRtiYo%(e)s+;}-J6K>zgN+)d-(Uw9ShC1Q zi_pKpMokts*{H?h784iQ9?ZDNeq-!8V~@5VD4s~A zMJm&Mo}mDO@5>{RmM3&u@;~?+bR4jOz6d1DWw0A65#+mudBrgEB6~Uwl%DJchhc7p zt^%ApKChu`6xDmS3FG+X8qf4@EZ7uc`N! zFVp)>oZ{mUxNu0!;g~mN0E{BHW(J5*32dS?8_0(Q%EmROA^bQ@x=hyCEnP1=3(#ql zoeS*NGlv~REL>#A4*RLWvg^z_E?GD6tI3`_%y*#aLvHmuc$WGL$gl`gm4K-r)MGkI z&~A`zY*;j@$XbpJyvpSs&oiauE(FLHcNJfSB= zY8MX2lnaY2FC@xRo@i5n*_ciS9EUm@BZ~L3NgA(mx^n?v5)2+DwBPCDG1Uz^XmoNp z57nOsoo~*exyA_x4}15wHiM0CdxJs$e(+uYF@cs6+nwHKu-*6l-n~9uBxR^_=5TAf zzw=;dlU*f-NnE-OTxtce(4kD|A}D>C@n$uSsqs7^NJ0n0%vyj{UuV|ESgYnP!DqR) z>|A#%H>}W!VFsWn34o^1drlaIDi45`EkMau2fRU1=R+aqchIdAdhk7=jZ>Cai)j_n zF;QZbW4RDxc>y>5M0{F^vebyh5v>qKmjw6w-ow5Z_&axpKlHr5W~CW7#gRxTD_d1H z=FF#uc|Pc1OQXRvMKeTtB#SK7sMfyFl-iXJ3$30=JwE>^Kwt{MA{Kr=9)nScx$ij( zE(HaYd9gIRs>}}6Dfm3LU241N`Cxx=aIn8KeTRYG5GEi>Jf~6|!vRc%TKk9eBUT6f zAvFBdqEO*HgOVJecTAV}tHV;4IgemE6j!j?MvKou98*`$Lc%HkpT|37Rfq(bT=8~g z3?-!8o0{sXbZ>jP=3=>mNoPYtk1JhZ(J5mS+i`^{NqGb@3vh!6L>cTDX^JyraY8;X zluFp$P8Lh^xn>UW^8n=lsIvJ;%i`0N#S_8u|r1C9LNhQ+5S!Dcz`n2^LhgZsd}e zRBqHnAF133*x=L0^?5g<$06Q<`W?s%URbZ{OPC|kn{s%lAULyAuRep#>Md9KO(Al) zEObBUAi(Di$LHS}AL+jmu&-KeEZ>%^gARhwz`LAbhb*`~R-*1kygc8hp!mSoV7e#`8sGo8+B6pQo(EWQ#EAoJ6Hu z1{llRUx7sE_f%+l_@e=Z0dr`#Iuu%xEM+|V?UzjHT6v7YZ@g$$F-b9wnJ_>RqW37+ zeBkwlTRQ{V7#aT7_JjMw-T+D$KiJrTN@fGceY&Xr(@5k9M0$Uk3JUpD{LponEBXw{ t+6lv-oAptMugxp`8~W*m18LOS?y^H4x8XKwEA^Gy3etM>W^=Lm?*DiX!V>@h literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/channel/channel_service_pb.py b/google_appengine/google/appengine/api/channel/channel_service_pb.py new file mode 100755 index 0000000..b96dd0e --- /dev/null +++ b/google_appengine/google/appengine/api/channel/channel_service_pb.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.api.api_base_pb import * +import google.appengine.api.api_base_pb +class ChannelServiceError(ProtocolBuffer.ProtocolMessage): + + OK = 0 + INTERNAL_ERROR = 1 + INVALID_CHANNEL_KEY = 2 + BAD_MESSAGE = 3 + CHANNEL_TIMEOUT = 4 + + _ErrorCode_NAMES = { + 0: "OK", + 1: "INTERNAL_ERROR", + 2: "INVALID_CHANNEL_KEY", + 3: "BAD_MESSAGE", + 4: "CHANNEL_TIMEOUT", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateChannelRequest(ProtocolBuffer.ProtocolMessage): + has_application_key_ = 0 + application_key_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def application_key(self): return self.application_key_ + + def set_application_key(self, x): + self.has_application_key_ = 1 + self.application_key_ = x + + def clear_application_key(self): + if self.has_application_key_: + self.has_application_key_ = 0 + self.application_key_ = "" + + def has_application_key(self): return self.has_application_key_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_application_key()): self.set_application_key(x.application_key()) + + def Equals(self, x): + if x is self: return 1 + if self.has_application_key_ != x.has_application_key_: return 0 + if self.has_application_key_ and self.application_key_ != x.application_key_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_application_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: application_key not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.application_key_)) + return n + 1 + + def Clear(self): + self.clear_application_key() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.application_key_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_application_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_application_key_: res+=prefix+("application_key: %s\n" % self.DebugFormatString(self.application_key_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapplication_key = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "application_key", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateChannelResponse(ProtocolBuffer.ProtocolMessage): + has_client_id_ = 0 + client_id_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def client_id(self): return self.client_id_ + + def set_client_id(self, x): + self.has_client_id_ = 1 + self.client_id_ = x + + def clear_client_id(self): + if self.has_client_id_: + self.has_client_id_ = 0 + self.client_id_ = "" + + def has_client_id(self): return self.has_client_id_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_client_id()): self.set_client_id(x.client_id()) + + def Equals(self, x): + if x is self: return 1 + if self.has_client_id_ != x.has_client_id_: return 0 + if self.has_client_id_ and self.client_id_ != x.client_id_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_client_id_): n += 1 + self.lengthString(len(self.client_id_)) + return n + 0 + + def Clear(self): + self.clear_client_id() + + def OutputUnchecked(self, out): + if (self.has_client_id_): + out.putVarInt32(18) + out.putPrefixedString(self.client_id_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 18: + self.set_client_id(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_client_id_: res+=prefix+("client_id: %s\n" % self.DebugFormatString(self.client_id_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kclient_id = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 2: "client_id", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class SendMessageRequest(ProtocolBuffer.ProtocolMessage): + has_application_key_ = 0 + application_key_ = "" + has_message_ = 0 + message_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def application_key(self): return self.application_key_ + + def set_application_key(self, x): + self.has_application_key_ = 1 + self.application_key_ = x + + def clear_application_key(self): + if self.has_application_key_: + self.has_application_key_ = 0 + self.application_key_ = "" + + def has_application_key(self): return self.has_application_key_ + + def message(self): return self.message_ + + def set_message(self, x): + self.has_message_ = 1 + self.message_ = x + + def clear_message(self): + if self.has_message_: + self.has_message_ = 0 + self.message_ = "" + + def has_message(self): return self.has_message_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_application_key()): self.set_application_key(x.application_key()) + if (x.has_message()): self.set_message(x.message()) + + def Equals(self, x): + if x is self: return 1 + if self.has_application_key_ != x.has_application_key_: return 0 + if self.has_application_key_ and self.application_key_ != x.application_key_: return 0 + if self.has_message_ != x.has_message_: return 0 + if self.has_message_ and self.message_ != x.message_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_application_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: application_key not set.') + if (not self.has_message_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: message not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.application_key_)) + n += self.lengthString(len(self.message_)) + return n + 2 + + def Clear(self): + self.clear_application_key() + self.clear_message() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.application_key_) + out.putVarInt32(18) + out.putPrefixedString(self.message_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_application_key(d.getPrefixedString()) + continue + if tt == 18: + self.set_message(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_application_key_: res+=prefix+("application_key: %s\n" % self.DebugFormatString(self.application_key_)) + if self.has_message_: res+=prefix+("message: %s\n" % self.DebugFormatString(self.message_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapplication_key = 1 + kmessage = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "application_key", + 2: "message", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['ChannelServiceError','CreateChannelRequest','CreateChannelResponse','SendMessageRequest'] diff --git a/google_appengine/google/appengine/api/channel/channel_service_pb.pyc b/google_appengine/google/appengine/api/channel/channel_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5c4ef815401fb1e1fcee66158ce33b05409fb44 GIT binary patch literal 18005 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/channel/channel_service_stub.py b/google_appengine/google/appengine/api/channel/channel_service_stub.py new file mode 100755 index 0000000..cb7d20d --- /dev/null +++ b/google_appengine/google/appengine/api/channel/channel_service_stub.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the Channel API, queues messages and writes them to a log.""" + + + + +import logging +import random + +from google.appengine.api import apiproxy_stub +from google.appengine.api.channel import channel_service_pb +from google.appengine.runtime import apiproxy_errors + + +class ChannelServiceStub(apiproxy_stub.APIProxyStub): + """Python only channel service stub. + + This stub does not use a browser channel to push messages to a client. + Instead it queues messages internally. + """ + + def __init__(self, log=logging.info, service_name='channel'): + """Initializer. + + Args: + log: A logger, used for dependency injection. + service_name: Service name expected for all calls. + """ + apiproxy_stub.APIProxyStub.__init__(self, service_name) + self._log = log + self._channel_messages = {} + + def _Dynamic_CreateChannel(self, request, response): + """Implementation of channel.get_channel. + + Args: + request: A ChannelServiceRequest. + response: A ChannelServiceResponse + """ + application_key = request.application_key() + if not application_key: + raise apiproxy_errors.ApplicationError( + channel_service_pb.ChannelServiceError.INVALID_CHANNEL_KEY) + + client_id = 'channel-%s-%s' % (random.randint(0, 2 ** 32), + application_key) + self._log('Creating channel id %s with application key %s', + client_id, request.application_key()) + + if application_key not in self._channel_messages: + self._channel_messages[application_key] = [] + + response.set_client_id(client_id) + + def _Dynamic_SendChannelMessage(self, request, response): + """Implementation of channel.send_message. + + Queues a message to be retrieved by the client when it polls. + + Args: + request: A SendMessageRequest. + response: A VoidProto. + """ + application_key = request.application_key() + self._log('Sending a message (%s) to channel with key (%s)', + request.message(), application_key) + + if not request.message(): + raise apiproxy_errors.ApplicationError( + channel_service_pb.ChannelServiceError.BAD_MESSAGE) + + if application_key not in self._channel_messages: + raise apiproxy_errors.ApplicationError( + channel_service_pb.ChannelServiceError.INVALID_CHANNEL_KEY) + + self._channel_messages[application_key].append(request.message()) + + def app_key_from_client_id(self, client_id): + """Returns the app key from a given client id. + + Args: + client_id: String representing a client id, returned by CreateChannel. + + Returns: + String representing the application key used to create this client_id, + or None if this client_id is incorrectly formed and doesn't map to an + application key. + """ + pieces = client_id.split('-', 2) + if len(pieces) == 3: + return pieces[2] + else: + return None + + def get_channel_messages(self, client_id): + """Returns the pending messages for a given channel. + + Args: + client_id: String representing the channel. Note that this is the id + returned by CreateChannel, not the application key. + + Returns: + List of messages, or None if the channel doesn't exist. The messages are + strings. + """ + self._log('Received request for messages for channel: ' + client_id) + app_key = self.app_key_from_client_id(client_id) + if app_key in self._channel_messages: + return self._channel_messages[app_key] + + return None + + def has_channel_messages(self, client_id): + """Checks to see if the given channel has any pending messages. + + Args: + client_id: String representing the channel. Note that this is the id + returned by CreateChannel, not the application key. + + Returns: + True if the channel exists and has pending messages. + """ + app_key = self.app_key_from_client_id(client_id) + has_messages = (app_key in self._channel_messages and + bool(self._channel_messages[app_key])) + self._log('Checking for messages on channel (%s) (%s)', + client_id, has_messages) + return has_messages + + def pop_first_message(self, client_id): + """Returns and clears the first message from the message queue. + + Args: + client_id: String representing the channel. Note that this is the id + returned by CreateChannel, not the application key. + + Returns: + The first message in the queue, or None if no messages. + """ + if self.has_channel_messages(client_id): + app_key = self.app_key_from_client_id(client_id) + self._log('Popping first message of queue for channel (%s)', client_id) + return self._channel_messages[app_key].pop(0) + + return None + + def clear_channel_messages(self, client_id): + """Clears all messages from the channel. + + Args: + client_id: String representing the channel. Note that this is the id + returned by CreateChannel, not the application key. + """ + app_key = self.app_key_from_client_id(client_id) + self._log('Clearing messages on channel (' + client_id + ')') + if app_key in self._channel_messages: + self._channel_messages[app_key] = [] diff --git a/google_appengine/google/appengine/api/channel/channel_service_stub.pyc b/google_appengine/google/appengine/api/channel/channel_service_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5615bba085e7a92ff22ccaa2df1e0ea2c48d9e0c GIT binary patch literal 6196 zcwW_*UvC^q5x{HquXp1R+aU>o=&nHuw*hA@q!SO(37s#o?tJ7{VfJ*=8Hv$&r+2sG z+38`XH_jsG0elkT6(0Bid=0(=4}2BA08~}?%PrG~if7P$5tLLBpy4n55 zgXsAP+;0n?kMYURFo^(8F!dm+!I=jqUXicCNsaS$IIBb4fRhGzRBpgo6XF(}v~0Nv zXKjeL;A9KSEszaR9T;Op8@>U!1o---OCt_@G;#hpCPOnn6|ZELE1d{E7G^5N(`lF_ zG8Ts~j`ziH=W;G{F_U>7PB0TDkvPwk!7R3&38O_QVm;}rf8%e^^Keje6O%AiX{KK< zf*hx?ulFz+*};QcX0Oyp2C1vNwcJH!na*-J0LK_J%k-!oIl9jB3 zYB=eAgVJk~=uiSECzKgw+9WK>Yyk?2G94!+lF$}sZa~!G%r^MLUY92ND<&@%W=gV3 z;)QUV5pFYrw%YG@h4^x+axRHTqofjT#5|WMsnblKW9zafiZ7k#(@MT9&rz&oV){7H zagrMuMnah@nox<6SrW$af*P83CEQWnNr#@NwD3nHVgTiVs=)=oMGY=%;9YoB2rC;6{2Ihg91JV-`as1Kg$ z^CZ?`lpjpAp2RW;Q{3%DCDOe(s9qq?2W|xi>)bOc>B4W}_&=tJCdAH?YBRX zrqIF$gffX#=Y}v^HFV!r^via6s*_x9YG`Y?VNQ<6&Gz_Vj(>`zst9N$Xt8|~X(n-* z$)q$w6^VzrI9FyWP7|qz?KupmzgGS5>wPu$@mcm>oKWTLWzpPy%KIdA_OLmLmOVwF zB5NQWs^cD3ery|s@O;ADP?M?`?dw`VH+OOT;MoGrON|0nfpil28BI^s(BAgiUdy{( z+w;0;2s^bMZ@0$g&~Oi%!=GWz9N4oV^cQRp_!5z2q>D0J3)_ilf@0ICti^%9#o(_u z>?}9rR@nSL*+DTjTLT@cw9fqR@Gfh}(J>az$eF{htXmHY$4)1}sYK#UrsOMhho=iZ zm95Ui`BWx!8l;+?;Q5P*oH43ej+wsAvNlqO^NF zdJ!+wa3)iP0bg2mm%aB1K%B|)w_>htV7HQT>`eo@Rjznc4iktZ9uu2jq*ShJzZeZe zfPqe=P~){0LQ&*1(piSV0)}81EX;5~3QH-}N^8c0P0 z8ym2svAMCq0ZvLB(ptFQ0(qxEHC1wiQN{HUNp2E*(3umE-m3%PH#9vN65QDHFpSs$ z;KyK(ba1}%-lcPDT*3n4c){@@isfYs^tP?$w}fNq|!tbt1y;JDtVKyu}2?cez|l_ z%48Ao^2S^ooBx#XL1q+rcIT^ESSylqGd#h#m%SjL`vTnI@4rX5t;n$@wis zcx9ZeY4R?^xa-~WcD!wi(vA71s1 z>$G7D4*D zQXM!2egDVjG_&$vv4NFHW7%(UO!vq}a%))pk4d1ojqU%6d-@v`aa;SAcL#Ck(Phbf z)S{13L14OEnCWO9Q?bp(NRM2ezQCbO*gdOctofsoPgDBeFD3CKDD33}N*=B3|5}9L zlM3FaiRi;_tJ$jU-RRuy-0SRi_VC%Yx5#};9#cY}Q*4C6ll9BVFsJOdzz1)s?Ykw| suP@)*)GC`Jyfb${<&P)z>lX>D`6H{?UyxMzplUU@F<{z37Iy0Y0W!l>Qvd(} literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/croninfo.py b/google_appengine/google/appengine/api/croninfo.py new file mode 100755 index 0000000..6967d06 --- /dev/null +++ b/google_appengine/google/appengine/api/croninfo.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""CronInfo tools. + +A library for working with CronInfo records, describing cron entries for an +application. Supports loading the records from yaml. +""" + + + +import logging +import sys +import traceback + +try: + import pytz +except ImportError: + pytz = None + +from google.appengine.cron import groc +from google.appengine.cron import groctimespecification +from google.appengine.api import validation +from google.appengine.api import yaml_builder +from google.appengine.api import yaml_listener +from google.appengine.api import yaml_object + +_URL_REGEX = r'^/.*$' +_TIMEZONE_REGEX = r'^.{0,100}$' +_DESCRIPTION_REGEX = r'^.{0,499}$' + + +class GrocValidator(validation.Validator): + """Checks that a schedule is in valid groc format.""" + + def Validate(self, value, key=None): + """Validates a schedule.""" + if value is None: + raise validation.MissingAttribute('schedule must be specified') + if not isinstance(value, basestring): + raise TypeError('schedule must be a string, not \'%r\''%type(value)) + try: + groctimespecification.GrocTimeSpecification(value) + except groc.GrocException, e: + raise validation.ValidationError('schedule \'%s\' failed to parse: %s'%( + value, e.args[0])) + return value + + +class TimezoneValidator(validation.Validator): + """Checks that a timezone can be correctly parsed and is known.""" + + def Validate(self, value, key=None): + """Validates a timezone.""" + if value is None: + return + if not isinstance(value, basestring): + raise TypeError('timezone must be a string, not \'%r\'' % type(value)) + if pytz is None: + return value + try: + pytz.timezone(value) + except pytz.UnknownTimeZoneError: + raise validation.ValidationError('timezone \'%s\' is unknown' % value) + except IOError: + return value + except: + unused_e, v, t = sys.exc_info() + logging.warning('pytz raised an unexpected error: %s.\n' % (v) + + 'Traceback:\n' + '\n'.join(traceback.format_tb(t))) + raise + return value + + +CRON = 'cron' + +URL = 'url' +SCHEDULE = 'schedule' +TIMEZONE = 'timezone' +DESCRIPTION = 'description' + + +class MalformedCronfigurationFile(Exception): + """Configuration file for Cron is malformed.""" + pass + + +class CronEntry(validation.Validated): + """A cron entry describes a single cron job.""" + ATTRIBUTES = { + URL: _URL_REGEX, + SCHEDULE: GrocValidator(), + TIMEZONE: TimezoneValidator(), + DESCRIPTION: validation.Optional(_DESCRIPTION_REGEX) + } + + +class CronInfoExternal(validation.Validated): + """CronInfoExternal describes all cron entries for an application.""" + ATTRIBUTES = { + CRON: validation.Optional(validation.Repeated(CronEntry)) + } + + +def LoadSingleCron(cron_info): + """Load a cron.yaml file or string and return a CronInfoExternal object.""" + builder = yaml_object.ObjectBuilder(CronInfoExternal) + handler = yaml_builder.BuilderHandler(builder) + listener = yaml_listener.EventListener(handler) + listener.Parse(cron_info) + + cron_info = handler.GetResults() + if len(cron_info) < 1: + raise MalformedCronfigurationFile('Empty cron configuration.') + if len(cron_info) > 1: + raise MalformedCronfigurationFile('Multiple cron sections ' + 'in configuration.') + return cron_info[0] diff --git a/google_appengine/google/appengine/api/croninfo.pyc b/google_appengine/google/appengine/api/croninfo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b031c75606591b4426ba89bd40a9f1dec2417f4c GIT binary patch literal 4863 zcwW6%ZFAek5nk|BqTVe_GG#YKr`DM1xR8^ko+OiLYRinH8ClAJa+6{@=77MFItc_g zxT9sJ(w8{>1D$^Af9a3t@94Mwfp&KffZ&(umsTak)9tf*cxA;?I3m^pWu(cqpz?lV}mG>*)Rq}onyeiCAbFv0r4Ze-rP!n}HZ9%dG7JhIF z$KwiE-_gPEw3f=~UYv64{cmupG z2-m^81>sHb+7NDl*ycT(#h%*`ZZWevJaMa-_z8sV(!_Q#@d1RliST|(itBamklO!@ z&0d|vUW?U_H0oR<6-ehxe6 z5}<3?jM5}9)J8^WITmUn0y)f>o9PxdFa1b{rLje9h;4r`l~E{CGrE+IMp9`J=fey6 za56X*fmU^-U+g-+xT}cu3+Imy?tk>)!DWg3av0Z>!)$gwsgaQrR-L`EoXCfD}CTwG+@r zQWA-}M@;^T%>_I4=^P~MU}*~^ANJaUA1sJ19IDb1RPO5vOtz7(lRUaXqqu7GYUOMV zbQ9!UHe-Doe+o4{53Cfvi2O6s!n}IM?emNJg?_rYrQ7 zt7|%}rsC4$snYg9*qNzCsDA#wY21%a7mx1SaiZ;eJLx_3v+}xoJL;Z2^kpPMbaH#* zr%HTo?`O>ya&R0^3s(#U%-)q=fC;*ipQ92j!oD`Cz?l>i*i zCZe0B=;OprBjpxuOYU)uZrdxntxFOV-qU>*hzSkrl`H)qdBxICN2*iHgvK8H;RS_iDhO z$X&vcdWw>3C8RD{n#IiexEhg?~fBQ z3X)AG!zAeUUFy3VxyaWeyi2Y}5L2YQhSjL(HSCa4zDLXdm)QLR-o~!Z7~6p#lbd3k z!(gYQneo67<6}s;K8x{A2FN5~0px#x0kXwFu5;??5z+{{l>)j|hHmX$=n|-ly@Wjm za~)*k3e0~(Fyly*!Mx466Ky8vGbrcWUVv582Jv&eh}qHN1;RQem_Jkjrc8$D>jB4Hmwer2@z)_k<}{w74*G&a#!GJXHDQnJ z%3gh!SYyDbbHJeg6g1YXEo+(k8&<>Gv6@)`aW|>iqK3@+|A`ZA$g0vJPEdzdux+Mk z#M;aAi+dZg(!mr%CWO=AGe06Dh>+^0p&U(9j^|%tCMtpqDV>ANGQ1gNFtE^^`VgDF z@^*WO`?TJ2ov+oF&-4y}hryPo5ui*&lf1c>h_~J38!|B7sJZ*w}ug30VCn-QJ$N z|IP9K(P74vyrMM9XFfVUcK07YKkoLf(=xwAUPEF^fr%_rMNqZy%f(Gz$ai09k)r&C zp~#MGDBAyqp)4|#WOK^NY{)^vy$`6NTFIrdl_SvpnBLu{=Ag@%cQni46mmEu z13yU2qvhuN)}X8gikxRiZ)`2sp&eAo^z#^L0-*aV$W&XD*BoFurn;X}LlwFEfSQk} z`6S;^yoT6DWgZ#sjYAOTt`YNW@Gs1pzK2n?YcB|d|H)||ayZ`_I literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/datastore.py b/google_appengine/google/appengine/api/datastore.py new file mode 100755 index 0000000..bd0b45b --- /dev/null +++ b/google_appengine/google/appengine/api/datastore.py @@ -0,0 +1,2576 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""The Python datastore API used by app developers. + +Defines Entity, Query, and Iterator classes, as well as methods for all of the +datastore's calls. Also defines conversions between the Python classes and +their PB counterparts. + +The datastore errors are defined in the datastore_errors module. That module is +only required to avoid circular imports. datastore imports datastore_types, +which needs BadValueError, so it can't be defined in datastore. +""" + + + + + + +import heapq +import itertools +import logging +import os +import re +import string +import sys +import traceback +from xml.sax import saxutils + +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_rpc +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import capabilities +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.datastore import datastore_index +from google.appengine.datastore import datastore_pb +from google.appengine.runtime import apiproxy_errors +from google.appengine.datastore import entity_pb + +try: + __import__('google.appengine.api.labs.taskqueue.taskqueue_service_pb') + taskqueue_service_pb = sys.modules.get( + 'google.appengine.api.labs.taskqueue.taskqueue_service_pb') +except ImportError: + from google.appengine.api.taskqueue import taskqueue_service_pb + +MAX_ALLOWABLE_QUERIES = 30 + +MAXIMUM_RESULTS = 1000 + +DEFAULT_TRANSACTION_RETRIES = 3 + +READ_CAPABILITY = capabilities.CapabilitySet('datastore_v3') +WRITE_CAPABILITY = capabilities.CapabilitySet( + 'datastore_v3', + capabilities=['write']) + +_MAX_INDEXED_PROPERTIES = 5000 + +_MAX_ID_BATCH_SIZE = 1000 * 1000 * 1000 + +Key = datastore_types.Key +typename = datastore_types.typename + +_txes = {} + +_ALLOWED_API_KWARGS = frozenset(['rpc']) + +_ALLOWED_FAILOVER_READ_METHODS = set( + ('Get', 'RunQuery', 'RunCompiledQuery', 'Count', 'Next')) + +ARBITRARY_FAILOVER_MS = -1 + +STRONG_CONSISTENCY = 0 +EVENTUAL_CONSISTENCY = 1 + +_MAX_INT_32 = 2**31-1 + + +def NormalizeAndTypeCheck(arg, types): + """Normalizes and type checks the given argument. + + Args: + arg: an instance or iterable of the given type(s) + types: allowed type or tuple of types + + Returns: + A (list, bool) tuple. The list is a normalized, shallow copy of the + argument. The boolean is True if the argument was a sequence, False + if it was a single object. + + Raises: + AssertionError: types includes list or tuple. + BadArgumentError: arg is not an instance or sequence of one of the given + types. + """ + if not isinstance(types, (list, tuple)): + types = (types,) + + assert list not in types and tuple not in types + + if isinstance(arg, types): + return [arg], False + else: + if isinstance(arg, basestring): + raise datastore_errors.BadArgumentError( + 'Expected an instance or iterable of %s; received %s (a %s).' % + (types, arg, typename(arg))) + + try: + arg_list = list(arg) + except TypeError: + raise datastore_errors.BadArgumentError( + 'Expected an instance or iterable of %s; received %s (a %s).' % + (types, arg, typename(arg))) + + for val in arg_list: + if not isinstance(val, types): + raise datastore_errors.BadArgumentError( + 'Expected one of %s; received %s (a %s).' % + (types, val, typename(val))) + + return arg_list, True + + +def NormalizeAndTypeCheckKeys(keys): + """Normalizes and type checks that the given argument is a valid key or keys. + + A wrapper around NormalizeAndTypeCheck() that accepts strings, Keys, and + Entities, and normalizes to Keys. + + Args: + keys: a Key or sequence of Keys + + Returns: + A (list of Keys, bool) tuple. See NormalizeAndTypeCheck. + + Raises: + BadArgumentError: arg is not an instance or sequence of one of the given + types. + """ + keys, multiple = NormalizeAndTypeCheck(keys, (basestring, Entity, Key)) + + keys = [_GetCompleteKeyOrError(key) for key in keys] + + return (keys, multiple) + + +def GetRpcFromKwargs(kwargs): + if not kwargs: + return None + args_diff = set(kwargs) - _ALLOWED_API_KWARGS + if args_diff: + raise TypeError('Invalid arguments: %s' % ', '.join(args_diff)) + return kwargs.get('rpc') + + +def _MakeSyncCall(service, call, request, response, rpc=None): + """The APIProxy entry point for a synchronous API call. + + Args: + service: string representing which service to call + call: string representing which function to call + request: protocol buffer for the request + response: protocol buffer for the response + rpc: datastore.DatastoreRPC to use for this request. + + Returns: + Response protocol buffer. Caller should always use returned value + which may or may not be same as passed in 'response'. + + Raises: + apiproxy_errors.Error or a subclass. + """ + if not rpc: + rpc = CreateRPC(service) + + rpc.make_call(call, request, response) + rpc.wait() + rpc.check_success() + return response + + +def CreateRPC(service='datastore_v3', deadline=None, callback=None, + read_policy=STRONG_CONSISTENCY): + """Create an rpc for use in configuring datastore calls. + + Args: + deadline: float, deadline for calls in seconds. + callback: callable, a callback triggered when this rpc completes, + accepts one argument: the returned rpc. + read_policy: flag, set to EVENTUAL_CONSISTENCY to enable eventually + consistent reads + + Returns: + A datastore.DatastoreRPC instance. + """ + return DatastoreRPC(service, deadline, callback, read_policy) + + +class DatastoreRPC(apiproxy_stub_map.UserRPC): + """Specialized RPC for the datastore. + + Wraps the default RPC class and sets appropriate values for use by the + datastore. + + This class or a sublcass of it is intended to be instatiated by + developers interested in setting specific request parameters, such as + deadline, on API calls. It will be used to make the actual call. + """ + + def __init__(self, service='datastore_v3', deadline=None, callback=None, + read_policy=STRONG_CONSISTENCY): + super(DatastoreRPC, self).__init__(service, deadline, callback) + self.read_policy = read_policy + + def make_call(self, call, request, response): + if self.read_policy == EVENTUAL_CONSISTENCY: + if call not in _ALLOWED_FAILOVER_READ_METHODS: + raise datastore_errors.BadRequestError( + 'read_policy is only supported on read operations.') + if call != 'Next': + request.set_failover_ms(ARBITRARY_FAILOVER_MS) + super(DatastoreRPC, self).make_call(call, request, response) + + def clone(self): + """Make a shallow copy of this instance. + + This is usually used when an RPC has been specified with some configuration + options and is being used as a template for multiple RPCs outside of a + developer's easy control. + """ + assert self.state == apiproxy_rpc.RPC.IDLE + return self.__class__( + self.service, self.deadline, self.callback, self.read_policy) + + +def Put(entities, **kwargs): + """Store one or more entities in the datastore. + + The entities may be new or previously existing. For new entities, Put() will + fill in the app id and key assigned by the datastore. + + If the argument is a single Entity, a single Key will be returned. If the + argument is a list of Entity, a list of Keys will be returned. + + Args: + entities: Entity or list of Entities + rpc: datastore.RPC to use for this request. + + Returns: + Key or list of Keys + + Raises: + TransactionFailedError, if the Put could not be committed. + """ + rpc = GetRpcFromKwargs(kwargs) + entities, multiple = NormalizeAndTypeCheck(entities, Entity) + + if multiple and not entities: + return [] + + for entity in entities: + if not entity.kind() or not entity.app(): + raise datastore_errors.BadRequestError( + 'App and kind must not be empty, in entity: %s' % entity) + + req = datastore_pb.PutRequest() + req.entity_list().extend([e._ToPb() for e in entities]) + + keys = [e.key() for e in entities] + tx = _MaybeSetupTransaction(req, keys) + + try: + resp = _MakeSyncCall( + 'datastore_v3', 'Put', req, datastore_pb.PutResponse(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + + keys = resp.key_list() + num_keys = len(keys) + num_entities = len(entities) + if num_keys != num_entities: + raise datastore_errors.InternalError( + 'Put accepted %d entities but returned %d keys.' % + (num_entities, num_keys)) + + for entity, key in zip(entities, keys): + entity._Entity__key._Key__reference.CopyFrom(key) + + if tx: + tx.entity_group = entities[0].entity_group() + + if multiple: + return [Key._FromPb(k) for k in keys] + else: + return Key._FromPb(resp.key(0)) + + +def Get(keys, **kwargs): + """Retrieves one or more entities from the datastore. + + Retrieves the entity or entities with the given key(s) from the datastore + and returns them as fully populated Entity objects, as defined below. If + there is an error, raises a subclass of datastore_errors.Error. + + If keys is a single key or string, an Entity will be returned, or + EntityNotFoundError will be raised if no existing entity matches the key. + + However, if keys is a list or tuple, a list of entities will be returned + that corresponds to the sequence of keys. It will include entities for keys + that were found and None placeholders for keys that were not found. + + Args: + # the primary key(s) of the entity(ies) to retrieve + keys: Key or string or list of Keys or strings + rpc: datastore.RPC to use for this request. + + Returns: + Entity or list of Entity objects + """ + rpc = GetRpcFromKwargs(kwargs) + keys, multiple = NormalizeAndTypeCheckKeys(keys) + + if multiple and not keys: + return [] + req = datastore_pb.GetRequest() + req.key_list().extend([key._Key__reference for key in keys]) + _MaybeSetupTransaction(req, keys) + + try: + resp = _MakeSyncCall( + 'datastore_v3', 'Get', req, datastore_pb.GetResponse(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + + entities = [] + for group in resp.entity_list(): + if group.has_entity(): + entities.append(Entity._FromPb(group.entity())) + else: + entities.append(None) + + if multiple: + return entities + else: + if entities[0] is None: + raise datastore_errors.EntityNotFoundError() + return entities[0] + + +def Delete(keys, **kwargs): + """Deletes one or more entities from the datastore. Use with care! + + Deletes the given entity(ies) from the datastore. You can only delete + entities from your app. If there is an error, raises a subclass of + datastore_errors.Error. + + Args: + # the primary key(s) of the entity(ies) to delete + keys: Key or string or list of Keys or strings + rpc: datastore.RPC to use for this request. + + Raises: + TransactionFailedError, if the Delete could not be committed. + """ + rpc = GetRpcFromKwargs(kwargs) + keys, multiple = NormalizeAndTypeCheckKeys(keys) + + if multiple and not keys: + return + + req = datastore_pb.DeleteRequest() + req.key_list().extend([key._Key__reference for key in keys]) + + tx = _MaybeSetupTransaction(req, keys) + + try: + _MakeSyncCall( + 'datastore_v3', 'Delete', req, datastore_pb.DeleteResponse(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + + +class Entity(dict): + """A datastore entity. + + Includes read-only accessors for app id, kind, and primary key. Also + provides dictionary-style access to properties. + """ + def __init__(self, kind, parent=None, _app=None, name=None, id=None, + unindexed_properties=[], namespace=None, **kwds): + """Constructor. Takes the kind and transaction root, which cannot be + changed after the entity is constructed, and an optional parent. Raises + BadArgumentError or BadKeyError if kind is invalid or parent is not an + existing Entity or Key in the datastore. + + Args: + # this entity's kind + kind: string + # if provided, this entity's parent. Its key must be complete. + parent: Entity or Key + # if provided, this entity's name. + name: string + # if provided, this entity's id. + id: integer + # if provided, a sequence of property names that should not be indexed + # by the built-in single property indices. + unindexed_properties: list or tuple of strings + namespace: string + # if provided, overrides the default namespace_manager setting. + """ + ref = entity_pb.Reference() + _app = datastore_types.ResolveAppId(_app) + ref.set_app(_app) + + _namespace = kwds.pop('_namespace', None) + if kwds: + raise datastore_errors.BadArgumentError( + 'Excess keyword arguments ' + repr(kwds)) + + if namespace is None: + namespace = _namespace + elif _namespace is not None: + raise datastore_errors.BadArgumentError( + "Must not set both _namespace and namespace parameters.") + + datastore_types.ValidateString(kind, 'kind', + datastore_errors.BadArgumentError) + if parent is not None: + parent = _GetCompleteKeyOrError(parent) + if _app != parent.app(): + raise datastore_errors.BadArgumentError( + " %s doesn't match parent's app %s" % + (_app, parent.app())) + if namespace is None: + namespace = parent.namespace() + elif namespace != parent.namespace(): + raise datastore_errors.BadArgumentError( + " %s doesn't match parent's namespace %s" % + (namespace, parent.namespace())) + ref.CopyFrom(parent._Key__reference) + + namespace = datastore_types.ResolveNamespace(namespace) + datastore_types.SetNamespace(ref, namespace) + + last_path = ref.mutable_path().add_element() + last_path.set_type(kind.encode('utf-8')) + + if name is not None and id is not None: + raise datastore_errors.BadArgumentError( + "Cannot set both name and id on an Entity") + + if name is not None: + datastore_types.ValidateString(name, 'name') + last_path.set_name(name.encode('utf-8')) + + if id is not None: + datastore_types.ValidateInteger(id, 'id') + last_path.set_id(id) + + self.set_unindexed_properties(unindexed_properties) + + self.__key = Key._FromPb(ref) + + def app(self): + """Returns the name of the application that created this entity, a + string or None if not set. + """ + return self.__key.app() + + def namespace(self): + """Returns the namespace of this entity, a string or None. + """ + return self.__key.namespace() + + def kind(self): + """Returns this entity's kind, a string. + """ + return self.__key.kind() + + def is_saved(self): + """Returns if this entity has been saved to the datastore + """ + last_path = self.__key._Key__reference.path().element_list()[-1] + return ((last_path.has_name() ^ last_path.has_id()) and + self.__key.has_id_or_name()) + + def key(self): + """Returns this entity's primary key, a Key instance. + """ + return self.__key + + def parent(self): + """Returns this entity's parent, as a Key. If this entity has no parent, + returns None. + """ + return self.key().parent() + + def entity_group(self): + """Returns this entity's entity group as a Key. + + Note that the returned Key will be incomplete if this is a a root entity + and its key is incomplete. + """ + return self.key().entity_group() + + def unindexed_properties(self): + """Returns this entity's unindexed properties, as a frozenset of strings.""" + return getattr(self, '_Entity__unindexed_properties', []) + + def set_unindexed_properties(self, unindexed_properties): + unindexed_properties, multiple = NormalizeAndTypeCheck(unindexed_properties, basestring) + if not multiple: + raise datastore_errors.BadArgumentError( + 'unindexed_properties must be a sequence; received %s (a %s).' % + (unindexed_properties, typename(unindexed_properties))) + for prop in unindexed_properties: + datastore_types.ValidateProperty(prop, None) + self.__unindexed_properties = frozenset(unindexed_properties) + + def __setitem__(self, name, value): + """Implements the [] operator. Used to set property value(s). + + If the property name is the empty string or not a string, raises + BadPropertyError. If the value is not a supported type, raises + BadValueError. + """ + datastore_types.ValidateProperty(name, value) + dict.__setitem__(self, name, value) + + def setdefault(self, name, value): + """If the property exists, returns its value. Otherwise sets it to value. + + If the property name is the empty string or not a string, raises + BadPropertyError. If the value is not a supported type, raises + BadValueError. + """ + datastore_types.ValidateProperty(name, value) + return dict.setdefault(self, name, value) + + def update(self, other): + """Updates this entity's properties from the values in other. + + If any property name is the empty string or not a string, raises + BadPropertyError. If any value is not a supported type, raises + BadValueError. + """ + for name, value in other.items(): + self.__setitem__(name, value) + + def copy(self): + """The copy method is not supported. + """ + raise NotImplementedError('Entity does not support the copy() method.') + + def ToXml(self): + """Returns an XML representation of this entity. Atom and gd:namespace + properties are converted to XML according to their respective schemas. For + more information, see: + + http://www.atomenabled.org/developers/syndication/ + http://code.google.com/apis/gdata/common-elements.html + + This is *not* optimized. It shouldn't be used anywhere near code that's + performance-critical. + """ + xml = u'' % (propname_xml, proptype_xml) + close_tag = u'' + xml_properties += [open_tag + val + close_tag for val in escaped_values] + + return xml_properties + + def _XmlEscapeValues(self, property): + """ Returns a list of the XML-escaped string values for the given property. + Raises an AssertionError if the property doesn't exist. + + Arg: + property: string + + Returns: + list of strings + """ + assert self.has_key(property) + xml = [] + + values = self[property] + if not isinstance(values, list): + values = [values] + + for val in values: + if hasattr(val, 'ToXml'): + xml.append(val.ToXml()) + else: + if val is None: + xml.append('') + else: + xml.append(saxutils.escape(unicode(val))) + + return xml + + def ToPb(self): + """Converts this Entity to its protocol buffer representation. + + Returns: + entity_pb.Entity + """ + return self._ToPb(False) + + def _ToPb(self, mark_key_as_saved=True): + """Converts this Entity to its protocol buffer representation. Not + intended to be used by application developers. + + Returns: + entity_pb.Entity + """ + + pb = entity_pb.EntityProto() + pb.mutable_key().CopyFrom(self.key()._ToPb()) + last_path = pb.key().path().element_list()[-1] + if mark_key_as_saved and last_path.has_name() and last_path.has_id(): + last_path.clear_id() + + group = pb.mutable_entity_group() + if self.__key.has_id_or_name(): + root = pb.key().path().element(0) + group.add_element().CopyFrom(root) + + properties = self.items() + properties.sort() + for (name, values) in properties: + properties = datastore_types.ToPropertyPb(name, values) + if not isinstance(properties, list): + properties = [properties] + + for prop in properties: + if (prop.meaning() in datastore_types._RAW_PROPERTY_MEANINGS or + name in self.unindexed_properties()): + pb.raw_property_list().append(prop) + else: + pb.property_list().append(prop) + + if pb.property_size() > _MAX_INDEXED_PROPERTIES: + raise datastore_errors.BadRequestError( + 'Too many indexed properties for entity %r.' % self.key()) + + return pb + + @staticmethod + def FromPb(pb): + """Static factory method. Returns the Entity representation of the + given protocol buffer (datastore_pb.Entity). + + Args: + pb: datastore_pb.Entity or str encoding of a datastore_pb.Entity + + Returns: + Entity: the Entity representation of pb + """ + if isinstance(pb, str): + real_pb = entity_pb.EntityProto() + real_pb.ParseFromString(pb) + pb = real_pb + + return Entity._FromPb(pb, require_valid_key=False) + + @staticmethod + def _FromPb(pb, require_valid_key=True): + """Static factory method. Returns the Entity representation of the + given protocol buffer (datastore_pb.Entity). Not intended to be used by + application developers. + + The Entity PB's key must be complete. If it isn't, an AssertionError is + raised. + + Args: + # a protocol buffer Entity + pb: datastore_pb.Entity + + Returns: + # the Entity representation of the argument + Entity + """ + assert pb.key().path().element_size() > 0 + + last_path = pb.key().path().element_list()[-1] + if require_valid_key: + assert last_path.has_id() ^ last_path.has_name() + if last_path.has_id(): + assert last_path.id() != 0 + else: + assert last_path.has_name() + assert last_path.name() + + unindexed_properties = [p.name() for p in pb.raw_property_list()] + + if pb.key().has_name_space(): + namespace = pb.key().name_space() + else: + namespace = '' + e = Entity(unicode(last_path.type().decode('utf-8')), + unindexed_properties=unindexed_properties, + _app=pb.key().app(), namespace=namespace) + ref = e.__key._Key__reference + ref.CopyFrom(pb.key()) + + temporary_values = {} + + for prop_list in (pb.property_list(), pb.raw_property_list()): + for prop in prop_list: + try: + value = datastore_types.FromPropertyPb(prop) + except (AssertionError, AttributeError, TypeError, ValueError), e: + raise datastore_errors.Error( + 'Property %s is corrupt in the datastore:\n%s' % + (prop.name(), traceback.format_exc())) + + multiple = prop.multiple() + if multiple: + value = [value] + + name = prop.name() + cur_value = temporary_values.get(name) + if cur_value is None: + temporary_values[name] = value + elif not multiple or not isinstance(cur_value, list): + raise datastore_errors.Error( + 'Property %s is corrupt in the datastore; it has multiple ' + 'values, but is not marked as multiply valued.' % name) + else: + cur_value.extend(value) + + for name, value in temporary_values.iteritems(): + decoded_name = unicode(name.decode('utf-8')) + + datastore_types.ValidateReadProperty(decoded_name, value) + + dict.__setitem__(e, decoded_name, value) + + return e + + +class Query(dict): + """A datastore query. + + (Instead of this, consider using appengine.ext.gql.Query! It provides a + query language interface on top of the same functionality.) + + Queries are used to retrieve entities that match certain criteria, including + app id, kind, and property filters. Results may also be sorted by properties. + + App id and kind are required. Only entities from the given app, of the given + type, are returned. If an ancestor is set, with Ancestor(), only entities + with that ancestor are returned. + + Property filters are used to provide criteria based on individual property + values. A filter compares a specific property in each entity to a given + value or list of possible values. + + An entity is returned if its property values match *all* of the query's + filters. In other words, filters are combined with AND, not OR. If an + entity does not have a value for a property used in a filter, it is not + returned. + + Property filters map filter strings of the form ' ' + to filter values. Use dictionary accessors to set property filters, like so: + + > query = Query('Person') + > query['name ='] = 'Ryan' + > query['age >='] = 21 + + This query returns all Person entities where the name property is 'Ryan', + 'Ken', or 'Bret', and the age property is at least 21. + + Another way to build this query is: + + > query = Query('Person') + > query.update({'name =': 'Ryan', 'age >=': 21}) + + The supported operators are =, >, <, >=, and <=. Only one inequality + filter may be used per query. Any number of equals filters may be used in + a single Query. + + A filter value may be a list or tuple of values. This is interpreted as + multiple filters with the same filter string and different values, all ANDed + together. For example, this query returns everyone with the tags "google" + and "app engine": + + > Query('Person', {'tag =': ('google', 'app engine')}) + + Result entities can be returned in different orders. Use the Order() + method to specify properties that results will be sorted by, and in which + direction. + + Note that filters and orderings may be provided at any time before the query + is run. When the query is fully specified, Run() runs the query and returns + an iterator. The query results can be accessed through the iterator. + + A query object may be reused after it's been run. Its filters and + orderings can be changed to create a modified query. + + If you know how many result entities you need, use Get() to fetch them: + + > query = Query('Person', {'age >': 21}) + > for person in query.Get(4): + > print 'I have four pints left. Have one on me, %s!' % person['name'] + + If you don't know how many results you need, or if you need them all, you + can get an iterator over the results by calling Run(): + + > for person in Query('Person', {'age >': 21}).Run(): + > print 'Have a pint on me, %s!' % person['name'] + + Get() is more efficient than Run(), so use Get() whenever possible. + + Finally, the Count() method returns the number of result entities matched by + the query. The returned count is cached; successive Count() calls will not + re-scan the datastore unless the query is changed. + """ + ASCENDING = datastore_pb.Query_Order.ASCENDING + DESCENDING = datastore_pb.Query_Order.DESCENDING + + ORDER_FIRST = datastore_pb.Query.ORDER_FIRST + ANCESTOR_FIRST = datastore_pb.Query.ANCESTOR_FIRST + FILTER_FIRST = datastore_pb.Query.FILTER_FIRST + + OPERATORS = {'<': datastore_pb.Query_Filter.LESS_THAN, + '<=': datastore_pb.Query_Filter.LESS_THAN_OR_EQUAL, + '>': datastore_pb.Query_Filter.GREATER_THAN, + '>=': datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL, + '=': datastore_pb.Query_Filter.EQUAL, + '==': datastore_pb.Query_Filter.EQUAL, + } + INEQUALITY_OPERATORS = frozenset(['<', '<=', '>', '>=']) + UPPERBOUND_INEQUALITY_OPERATORS = frozenset(['<', '<=']) + FILTER_REGEX = re.compile( + '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(OPERATORS.keys()), + re.IGNORECASE | re.UNICODE) + + __kind = None + __app = None + __namespace = None + __orderings = None + __cached_count = None + __hint = None + __ancestor = None + __compile = None + + __cursor = None + __end_cursor = None + + __filter_order = None + __filter_counter = 0 + + __inequality_prop = None + __inequality_count = 0 + + def __init__(self, kind=None, filters={}, _app=None, keys_only=False, + compile=True, cursor=None, namespace=None, end_cursor=None, + **kwds): + """Constructor. + + Raises BadArgumentError if kind is not a string. Raises BadValueError or + BadFilterError if filters is not a dictionary of valid filters. + + Args: + # kind is required. filters is optional; if provided, it's used + # as an initial set of property filters. keys_only defaults to False. + kind: string + filters: dict + keys_only: boolean + namespace: string + """ + + _namespace = kwds.pop('_namespace', None) + if kwds: + raise datastore_errors.BadArgumentError( + 'Excess keyword arguments ' + repr(kwds)) + + if namespace is None: + namespace = _namespace + elif _namespace is not None: + raise datastore_errors.BadArgumentError( + "Must not set both _namespace and namespace parameters.") + + if kind is not None: + datastore_types.ValidateString(kind, 'kind', + datastore_errors.BadArgumentError) + + self.__kind = kind + self.__orderings = [] + self.__filter_order = {} + self.update(filters) + + self.__app = datastore_types.ResolveAppId(_app) + self.__namespace = datastore_types.ResolveNamespace(namespace) + self.__keys_only = keys_only + self.__compile = compile + self.__cursor = cursor + self.__end_cursor = end_cursor + + def Order(self, *orderings): + """Specify how the query results should be sorted. + + Result entities will be sorted by the first property argument, then by the + second, and so on. For example, this: + + > query = Query('Person') + > query.Order('bday', ('age', Query.DESCENDING)) + + sorts everyone in order of their birthday, starting with January 1. + People with the same birthday are sorted by age, oldest to youngest. + + The direction for each sort property may be provided; if omitted, it + defaults to ascending. + + Order() may be called multiple times. Each call resets the sort order + from scratch. + + If an inequality filter exists in this Query it must be the first property + passed to Order. Any number of sort orders may be used after the + inequality filter property. Without inequality filters, any number of + filters with different orders may be specified. + + Entities with multiple values for an order property are sorted by their + lowest value. + + Note that a sort order implies an existence filter! In other words, + Entities without the sort order property are filtered out, and *not* + included in the query results. + + If the sort order property has different types in different entities - ie, + if bob['id'] is an int and fred['id'] is a string - the entities will be + grouped first by the property type, then sorted within type. No attempt is + made to compare property values across types. + + Raises BadArgumentError if any argument is of the wrong format. + + Args: + # the properties to sort by, in sort order. each argument may be either a + # string or (string, direction) 2-tuple. + + Returns: + # this query + Query + """ + orderings = list(orderings) + + for (order, i) in zip(orderings, range(len(orderings))): + if not (isinstance(order, basestring) or + (isinstance(order, tuple) and len(order) in [2, 3])): + raise datastore_errors.BadArgumentError( + 'Order() expects strings or 2- or 3-tuples; received %s (a %s). ' % + (order, typename(order))) + + if isinstance(order, basestring): + order = (order,) + + datastore_types.ValidateString(order[0], 'sort order property', + datastore_errors.BadArgumentError) + property = order[0] + + direction = order[-1] + if direction not in (Query.ASCENDING, Query.DESCENDING): + if len(order) == 3: + raise datastore_errors.BadArgumentError( + 'Order() expects Query.ASCENDING or DESCENDING; received %s' % + str(direction)) + direction = Query.ASCENDING + + if (self.__kind is None and + (property != datastore_types._KEY_SPECIAL_PROPERTY or + direction != Query.ASCENDING)): + raise datastore_errors.BadArgumentError( + 'Only %s ascending orders are supported on kindless queries' % + datastore_types._KEY_SPECIAL_PROPERTY) + + orderings[i] = (property, direction) + + if (orderings and self.__inequality_prop and + orderings[0][0] != self.__inequality_prop): + raise datastore_errors.BadArgumentError( + 'First ordering property must be the same as inequality filter ' + 'property, if specified for this query; received %s, expected %s' % + (orderings[0][0], self.__inequality_prop)) + + self.__orderings = orderings + return self + + def Hint(self, hint): + """Sets a hint for how this query should run. + + The query hint gives us information about how best to execute your query. + Currently, we can only do one index scan, so the query hint should be used + to indicates which index we should scan against. + + Use FILTER_FIRST if your first filter will only match a few results. In + this case, it will be most efficient to scan against the index for this + property, load the results into memory, and apply the remaining filters + and sort orders there. + + Similarly, use ANCESTOR_FIRST if the query's ancestor only has a few + descendants. In this case, it will be most efficient to scan all entities + below the ancestor and load them into memory first. + + Use ORDER_FIRST if the query has a sort order and the result set is large + or you only plan to fetch the first few results. In that case, we + shouldn't try to load all of the results into memory; instead, we should + scan the index for this property, which is in sorted order. + + Note that hints are currently ignored in the v3 datastore! + + Arg: + one of datastore.Query.[ORDER_FIRST, ANCESTOR_FIRST, FILTER_FIRST] + + Returns: + # this query + Query + """ + if hint not in [self.ORDER_FIRST, self.ANCESTOR_FIRST, self.FILTER_FIRST]: + raise datastore_errors.BadArgumentError( + 'Query hint must be ORDER_FIRST, ANCESTOR_FIRST, or FILTER_FIRST.') + + self.__hint = hint + return self + + def Ancestor(self, ancestor): + """Sets an ancestor for this query. + + This restricts the query to only return result entities that are descended + from a given entity. In other words, all of the results will have the + ancestor as their parent, or parent's parent, or etc. + + Raises BadArgumentError or BadKeyError if parent is not an existing Entity + or Key in the datastore. + + Args: + # the key must be complete + ancestor: Entity or Key + + Returns: + # this query + Query + """ + self.__ancestor = _GetCompleteKeyOrError(ancestor) + return self + + def IsKeysOnly(self): + """Returns True if this query is keys only, false otherwise.""" + return self.__keys_only + + def GetCompiledCursor(self): + try: + compiled_cursor = self.__last_iterator.GetCompiledCursor(self) + if not compiled_cursor: + raise AttributeError() + except AttributeError: + raise AssertionError('No cursor available, either this query has not ' + 'been executed or there is no compilation ' + 'available for this kind of query') + return compiled_cursor + + def GetCompiledQuery(self): + try: + if not self.__compiled_query: + raise AttributeError() + except AttributeError: + raise AssertionError('No compiled query available, either this query has ' + 'not been executed or there is no compilation ' + 'available for this kind of query') + return self.__compiled_query + + def Run(self, **kwargs): + """Runs this query. + + If a filter string is invalid, raises BadFilterError. If a filter value is + invalid, raises BadValueError. If an IN filter is provided, and a sort + order on another property is provided, raises BadQueryError. + + If you know in advance how many results you want, use Get() instead. It's + more efficient. + + Args: + limit: integer, limit for the query. + offset: integer, offset for the query. + prefetch_count: integer, number of results to return in the first query. + next_count: number of results to return in subsequent next queries. + rpc: datastore.RPC to use for this request. + + Returns: + # an iterator that provides access to the query results + Iterator + """ + return self._Run(**kwargs) + + def _Run(self, limit=None, offset=None, + prefetch_count=None, next_count=None, **kwargs): + """Runs this query, with an optional result limit and an optional offset. + + Identical to Run, with the extra optional limit, offset, prefetch_count, + next_count parameters. These parameters must be integers >= 0. + + This is not intended to be used by application developers. Use Get() + instead! + + Args: + limit: integer, limit for the query. + offset: integer, offset for the query. + prefetch_count: integer, number of results to return in the first query. + next_count: number of results to return in subsequent next queries. + rpc: datastore.RPC to use for this request. + """ + rpc = GetRpcFromKwargs(kwargs) + request = self._ToPb(limit, offset, prefetch_count) + + if rpc: + rpc_clone = rpc.clone() + else: + rpc_clone = None + + try: + result = _MakeSyncCall('datastore_v3', 'RunQuery', request, + datastore_pb.QueryResult(), rpc) + except apiproxy_errors.ApplicationError, err: + try: + raise _ToDatastoreError(err) + except datastore_errors.NeedIndexError, exc: + yaml = datastore_index.IndexYamlForQuery( + *datastore_index.CompositeIndexForQuery(request)[1:-1]) + raise datastore_errors.NeedIndexError( + str(exc) + '\nThis query needs this index:\n' + yaml) + + if result.has_cursor() and not result.cursor().app(): + result.mutable_cursor().set_app(self.__app) + + if result.has_compiled_query(): + self.__compiled_query = result.compiled_query() + else: + self.__compiled_query = None + + self.__last_iterator = Iterator( + result, query_request_pb=request, batch_size=next_count, rpc=rpc_clone) + return self.__last_iterator + + def Get(self, limit, offset=0, **kwargs): + """Fetches and returns a maximum number of results from the query. + + This method fetches and returns a list of resulting entities that matched + the query. If the query specified a sort order, entities are returned in + that order. Otherwise, the order is undefined. + + The limit argument specifies the maximum number of entities to return. If + it's greater than the number of remaining entities, all of the remaining + entities are returned. In that case, the length of the returned list will + be smaller than limit. + + The offset argument specifies the number of entities that matched the + query criteria to skip before starting to return results. The limit is + applied after the offset, so if you provide a limit of 10 and an offset of 5 + and your query matches 20 records, the records whose index is 0 through 4 + will be skipped and the records whose index is 5 through 14 will be + returned. + + The results are always returned as a list. If there are no results left, + an empty list is returned. + + If you know in advance how many results you want, this method is more + efficient than Run(), since it fetches all of the results at once. (The + datastore backend sets the the limit on the underlying + scan, which makes the scan significantly faster.) + + Args: + # the maximum number of entities to return + int or long + # the number of entities to skip + int or long + rpc: datastore.RPC to use for this request. + + Returns: + # a list of entities + [Entity, ...] + """ + rpc = GetRpcFromKwargs(kwargs) + + if not isinstance(limit, (int, long)) or limit < 0: + raise datastore_errors.BadArgumentError( + 'Argument to Get named \'limit\' must be an int greater than or ' + 'equal to 0; received %s (a %s)' % (limit, typename(limit))) + + if not isinstance(offset, (int, long)) or offset < 0: + raise datastore_errors.BadArgumentError( + 'Argument to Get named \'offset\' must be an int greater than or ' + 'equal to 0; received %s (a %s)' % (offset, typename(offset))) + + return self._Run( + limit=limit, offset=offset, prefetch_count=limit, **kwargs)._Get(limit) + + def Count(self, limit=1000, **kwargs): + """Returns the number of entities that this query matches. The returned + count is cached; successive Count() calls will not re-scan the datastore + unless the query is changed. + + Args: + limit, a number or None. If there are more results than this, stop short + and just return this number. Providing this argument makes the count + operation more efficient. + rpc: datastore.RPC to use for this request. + + Returns: + The number of results. + """ + if not self.__cached_count: + if limit is None: + offset = _MAX_INT_32 + else: + offset = limit + + iterator = self._Run(limit=0, offset=offset, **kwargs) + self.__cached_count = iterator._SkippedResults() + + return self.__cached_count + + def __iter__(self): + raise NotImplementedError( + 'Query objects should not be used as iterators. Call Run() first.') + + def __setitem__(self, filter, value): + """Implements the [] operator. Used to set filters. + + If the filter string is empty or not a string, raises BadFilterError. If + the value is not a supported type, raises BadValueError. + """ + if isinstance(value, tuple): + value = list(value) + + datastore_types.ValidateProperty(' ', value, read_only=True) + match = self._CheckFilter(filter, value) + property = match.group(1) + operator = match.group(3) + + dict.__setitem__(self, filter, value) + + if (operator in self.INEQUALITY_OPERATORS and + property != datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY): + if self.__inequality_prop is None: + self.__inequality_prop = property + else: + assert self.__inequality_prop == property + self.__inequality_count += 1 + + if filter not in self.__filter_order: + self.__filter_order[filter] = self.__filter_counter + self.__filter_counter += 1 + + self.__cached_count = None + + def setdefault(self, filter, value): + """If the filter exists, returns its value. Otherwise sets it to value. + + If the property name is the empty string or not a string, raises + BadPropertyError. If the value is not a supported type, raises + BadValueError. + """ + datastore_types.ValidateProperty(' ', value) + self._CheckFilter(filter, value) + self.__cached_count = None + return dict.setdefault(self, filter, value) + + def __delitem__(self, filter): + """Implements the del [] operator. Used to remove filters. + """ + dict.__delitem__(self, filter) + del self.__filter_order[filter] + self.__cached_count = None + + match = Query.FILTER_REGEX.match(filter) + property = match.group(1) + operator = match.group(3) + + if operator in self.INEQUALITY_OPERATORS: + assert self.__inequality_count >= 1 + assert property == self.__inequality_prop + self.__inequality_count -= 1 + if self.__inequality_count == 0: + self.__inequality_prop = None + + def update(self, other): + """Updates this query's filters from the ones in other. + + If any filter string is invalid, raises BadFilterError. If any value is + not a supported type, raises BadValueError. + """ + for filter, value in other.items(): + self.__setitem__(filter, value) + + def copy(self): + """The copy method is not supported. + """ + raise NotImplementedError('Query does not support the copy() method.') + + def _CheckFilter(self, filter, values): + """Type check a filter string and list of values. + + Raises BadFilterError if the filter string is empty, not a string, or + invalid. Raises BadValueError if the value type is not supported. + + Args: + filter: String containing the filter text. + values: List of associated filter values. + + Returns: + re.MatchObject (never None) that matches the 'filter'. Group 1 is the + property name, group 3 is the operator. (Group 2 is unused.) + """ + try: + match = Query.FILTER_REGEX.match(filter) + if not match: + raise datastore_errors.BadFilterError( + 'Could not parse filter string: %s' % str(filter)) + except TypeError: + raise datastore_errors.BadFilterError( + 'Could not parse filter string: %s' % str(filter)) + + property = match.group(1) + operator = match.group(3) + if operator is None: + operator = '=' + + if isinstance(values, tuple): + values = list(values) + elif not isinstance(values, list): + values = [values] + if isinstance(values[0], datastore_types._RAW_PROPERTY_TYPES): + raise datastore_errors.BadValueError( + 'Filtering on %s properties is not supported.' % typename(values[0])) + + if (operator in self.INEQUALITY_OPERATORS and + property != datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY): + if self.__inequality_prop and property != self.__inequality_prop: + raise datastore_errors.BadFilterError( + 'Only one property per query may have inequality filters (%s).' % + ', '.join(self.INEQUALITY_OPERATORS)) + elif len(self.__orderings) >= 1 and self.__orderings[0][0] != property: + raise datastore_errors.BadFilterError( + 'Inequality operators (%s) must be on the same property as the ' + 'first sort order, if any sort orders are supplied' % + ', '.join(self.INEQUALITY_OPERATORS)) + + if (self.__kind is None and + property != datastore_types._KEY_SPECIAL_PROPERTY and + property != datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY): + raise datastore_errors.BadFilterError( + 'Only %s filters are allowed on kindless queries.' % + datastore_types._KEY_SPECIAL_PROPERTY) + + if property == datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY: + if self.__kind: + raise datastore_errors.BadFilterError( + 'Only kindless queries can have %s filters.' % + datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY) + if not operator in self.UPPERBOUND_INEQUALITY_OPERATORS: + raise datastore_errors.BadFilterError( + 'Only %s operators are supported with %s filters.' % ( + self.UPPERBOUND_INEQUALITY_OPERATORS, + datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY)) + + if property in datastore_types._SPECIAL_PROPERTIES: + if property == datastore_types._KEY_SPECIAL_PROPERTY: + for value in values: + if not isinstance(value, Key): + raise datastore_errors.BadFilterError( + '%s filter value must be a Key; received %s (a %s)' % + (datastore_types._KEY_SPECIAL_PROPERTY, value, typename(value))) + + return match + + def _ToPb(self, limit=None, offset=None, count=None): + """Converts this Query to its protocol buffer representation. Not + intended to be used by application developers. Enforced by hiding the + datastore_pb classes. + + Args: + # an upper bound on the number of results returned by the query. + limit: int + # number of results that match the query to skip. limit is applied + # after the offset is fulfilled + offset: int + # the requested initial batch size + count: int + + Returns: + # the PB representation of this Query + datastore_pb.Query + + Raises: + BadRequestError if called inside a transaction and the query does not + include an ancestor. + """ + + if not self.__ancestor and IsInTransaction(): + raise datastore_errors.BadRequestError( + 'Only ancestor queries are allowed inside transactions.') + + pb = datastore_pb.Query() + _MaybeSetupTransaction(pb, [self.__ancestor]) + + if self.__kind is not None: + pb.set_kind(self.__kind.encode('utf-8')) + pb.set_keys_only(bool(self.__keys_only)) + if self.__app: + pb.set_app(self.__app.encode('utf-8')) + datastore_types.SetNamespace(pb, self.__namespace) + if self.__compile: + pb.set_compile(True) + if limit is not None: + pb.set_limit(limit) + if offset is not None: + pb.set_offset(offset) + if count is not None: + pb.set_count(count) + if self.__ancestor: + pb.mutable_ancestor().CopyFrom(self.__ancestor._Key__reference) + + if ((self.__hint == self.ORDER_FIRST and self.__orderings) or + (self.__hint == self.ANCESTOR_FIRST and self.__ancestor) or + (self.__hint == self.FILTER_FIRST and len(self) > 0)): + pb.set_hint(self.__hint) + + ordered_filters = [(i, f) for f, i in self.__filter_order.iteritems()] + ordered_filters.sort() + + for i, filter_str in ordered_filters: + if filter_str not in self: + continue + + values = self[filter_str] + match = self._CheckFilter(filter_str, values) + name = match.group(1) + + props = datastore_types.ToPropertyPb(name, values) + if not isinstance(props, list): + props = [props] + + op = match.group(3) + if op is None: + op = '=' + + for prop in props: + filter = pb.add_filter() + filter.set_op(self.OPERATORS[op]) + filter.add_property().CopyFrom(prop) + + for property, direction in self.__orderings: + order = pb.add_order() + order.set_property(property.encode('utf-8')) + order.set_direction(direction) + + if self.__cursor: + pb.mutable_compiled_cursor().CopyFrom(self.__cursor) + if self.__end_cursor: + pb.mutable_end_compiled_cursor().CopyFrom(self.__end_cursor) + return pb + + +def AllocateIds(model_key, size=None, **kwargs): + """Allocates a range of IDs of size or with max for the given key. + + Allocates a range of IDs in the datastore such that those IDs will not + be automatically assigned to new entities. You can only allocate IDs + for model keys from your app. If there is an error, raises a subclass of + datastore_errors.Error. + + Either size or max must be provided but not both. If size is provided then a + range of the given size is returned. If max is provided then the largest + range of ids that are safe to use with an upper bound of max is returned (can + be an empty range). + + Max should only be provided if you have an existing numeric id range that you + want to reserve, e.g. bulk loading entities that already have IDs. If you + don't care about which IDs you receive, use size instead. + + Args: + model_key: Key or string to serve as a model specifying the ID sequence + in which to allocate IDs + size: integer, number of IDs to allocate. + max: integer, upper bound of the range of IDs to allocate. + rpc: datastore.RPC to use for this request. + + Returns: + (start, end) of the allocated range, inclusive. + """ + max = kwargs.pop('max', None) + rpc = GetRpcFromKwargs(kwargs) + keys, _ = NormalizeAndTypeCheckKeys(model_key) + + if len(keys) > 1: + raise datastore_errors.BadArgumentError( + 'Cannot allocate IDs for more than one model key at a time') + + req = datastore_pb.AllocateIdsRequest() + if size is not None: + if max is not None: + raise datastore_errors.BadArgumentError( + 'Cannot allocate ids using both size and max') + if size > _MAX_ID_BATCH_SIZE: + raise datastore_errors.BadArgumentError( + 'Cannot allocate more than %s ids at a time; received %s' + % (_MAX_ID_BATCH_SIZE, size)) + if size <= 0: + raise datastore_errors.BadArgumentError( + 'Cannot allocate less than 1 id; received %s' % size) + req.set_size(size) + if max: + if max < 0: + raise datastore_errors.BadArgumentError( + 'Cannot allocate a range with a max less than 0 id; received %s' % + size) + req.set_max(max) + + req.mutable_model_key().CopyFrom(keys[0]._ToPb()) + + try: + resp = _MakeSyncCall('datastore_v3', 'AllocateIds', req, + datastore_pb.AllocateIdsResponse(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + + return resp.start(), resp.end() + + +class MultiQuery(Query): + """Class representing a query which requires multiple datastore queries. + + This class is actually a subclass of datastore.Query as it is intended to act + like a normal Query object (supporting the same interface). + + Does not support keys only queries, since it needs whole entities in order + to merge sort them. (That's not true if there are no sort orders, or if the + sort order is on __key__, but allowing keys only queries in those cases, but + not in others, would be confusing.) + """ + + def __init__(self, bound_queries, orderings): + if len(bound_queries) > MAX_ALLOWABLE_QUERIES: + raise datastore_errors.BadArgumentError( + 'Cannot satisfy query -- too many subqueries (max: %d, got %d).' + ' Probable cause: too many IN/!= filters in query.' % + (MAX_ALLOWABLE_QUERIES, len(bound_queries))) + + for query in bound_queries: + if query.IsKeysOnly(): + raise datastore_errors.BadQueryError( + 'MultiQuery does not support keys_only.') + + self.__bound_queries = bound_queries + self.__orderings = orderings + self.__compile = False + + def __str__(self): + res = 'MultiQuery: ' + for query in self.__bound_queries: + res = '%s %s' % (res, str(query)) + return res + + def Get(self, limit, offset=0, **kwargs): + """Get results of the query with a limit on the number of results. + + Args: + limit: maximum number of values to return. + offset: offset requested -- if nonzero, this will override the offset in + the original query + rpc: datastore.RPC to use for this request. + + Returns: + A list of entities with at most "limit" entries (less if the query + completes before reading limit values). + """ + rpc = GetRpcFromKwargs(kwargs) + count = 1 + result = [] + + iterator = self.Run(rpc=rpc) + + try: + for i in xrange(offset): + val = iterator.next() + except StopIteration: + pass + + try: + while count <= limit: + val = iterator.next() + result.append(val) + count += 1 + except StopIteration: + pass + return result + + class SortOrderEntity(object): + """Allow entity comparisons using provided orderings. + + The iterator passed to the constructor is eventually consumed via + calls to GetNext(), which generate new SortOrderEntity s with the + same orderings. + """ + + def __init__(self, entity_iterator, orderings): + """Ctor. + + Args: + entity_iterator: an iterator of entities which will be wrapped. + orderings: an iterable of (identifier, order) pairs. order + should be either Query.ASCENDING or Query.DESCENDING. + """ + self.__entity_iterator = entity_iterator + self.__entity = None + self.__min_max_value_cache = {} + try: + self.__entity = entity_iterator.next() + except StopIteration: + pass + else: + self.__orderings = orderings + + def __str__(self): + return str(self.__entity) + + def GetEntity(self): + """Gets the wrapped entity.""" + return self.__entity + + def GetNext(self): + """Wrap and return the next entity. + + The entity is retrieved from the iterator given at construction time. + """ + return MultiQuery.SortOrderEntity(self.__entity_iterator, + self.__orderings) + + def CmpProperties(self, that): + """Compare two entities and return their relative order. + + Compares self to that based on the current sort orderings and the + key orders between them. Returns negative, 0, or positive depending on + whether self is less, equal to, or greater than that. This + comparison returns as if all values were to be placed in ascending order + (highest value last). Only uses the sort orderings to compare (ignores + keys). + + Args: + that: SortOrderEntity + + Returns: + Negative if self < that + Zero if self == that + Positive if self > that + """ + if not self.__entity: + return cmp(self.__entity, that.__entity) + + for (identifier, order) in self.__orderings: + value1 = self.__GetValueForId(self, identifier, order) + value2 = self.__GetValueForId(that, identifier, order) + + result = cmp(value1, value2) + if order == Query.DESCENDING: + result = -result + if result: + return result + return 0 + + def __GetValueForId(self, sort_order_entity, identifier, sort_order): + value = _GetPropertyValue(sort_order_entity.__entity, identifier) + if isinstance(value, list): + entity_key = sort_order_entity.__entity.key() + if (entity_key, identifier) in self.__min_max_value_cache: + value = self.__min_max_value_cache[(entity_key, identifier)] + elif sort_order == Query.DESCENDING: + value = min(value) + else: + value = max(value) + self.__min_max_value_cache[(entity_key, identifier)] = value + + return value + + def __cmp__(self, that): + """Compare self to that w.r.t. values defined in the sort order. + + Compare an entity with another, using sort-order first, then the key + order to break ties. This can be used in a heap to have faster min-value + lookup. + + Args: + that: other entity to compare to + Returns: + negative: if self is less than that in sort order + zero: if self is equal to that in sort order + positive: if self is greater than that in sort order + """ + property_compare = self.CmpProperties(that) + if property_compare: + return property_compare + else: + return cmp(self.__entity.key(), that.__entity.key()) + + def Run(self, **kwargs): + """Return an iterable output with all results in order. + + Merge sort the results. First create a list of iterators, then walk + though them and yield results in order. + """ + rpc = GetRpcFromKwargs(kwargs) + results = [] + count = 1 + log_level = logging.DEBUG - 1 + for bound_query in self.__bound_queries: + logging.log(log_level, 'Running query #%i' % count) + if rpc: + rpc_clone = rpc.clone() + else: + rpc_clone = None + results.append(bound_query.Run(rpc=rpc_clone)) + count += 1 + + def IterateResults(results): + """Iterator function to return all results in sorted order. + + Iterate over the array of results, yielding the next element, in + sorted order. This function is destructive (results will be empty + when the operation is complete). + + Args: + results: list of result iterators to merge and iterate through + + Yields: + The next result in sorted order. + """ + result_heap = [] + for result in results: + heap_value = MultiQuery.SortOrderEntity(result, self.__orderings) + if heap_value.GetEntity(): + heapq.heappush(result_heap, heap_value) + + used_keys = set() + + while result_heap: + top_result = heapq.heappop(result_heap) + + results_to_push = [] + if top_result.GetEntity().key() not in used_keys: + yield top_result.GetEntity() + else: + pass + + used_keys.add(top_result.GetEntity().key()) + + results_to_push = [] + while result_heap: + next = heapq.heappop(result_heap) + if cmp(top_result, next): + results_to_push.append(next) + break + else: + results_to_push.append(next.GetNext()) + results_to_push.append(top_result.GetNext()) + + for popped_result in results_to_push: + if popped_result.GetEntity(): + heapq.heappush(result_heap, popped_result) + + return IterateResults(results) + + def Count(self, limit=None, **kwargs): + """Return the number of matched entities for this query. + + Will return the de-duplicated count of results. Will call the more + efficient Get() function if a limit is given. + + Args: + limit: maximum number of entries to count (for any result > limit, return + limit). + rpc: datastore.RPC to use for this request. + + Returns: + count of the number of entries returned. + """ + rpc = GetRpcFromKwargs(kwargs) + if limit is None: + count = 0 + for i in self.Run(rpc=rpc): + count += 1 + return count + else: + return len(self.Get(limit, rpc=rpc)) + + def GetCompiledCursor(self): + raise AssertionError('No cursor available for a MultiQuery (queries ' + 'using "IN" or "!=" operators)') + + def GetCompiledQuery(self): + raise AssertionError('No compilation available for a MultiQuery (queries ' + 'using "IN" or "!=" operators)') + + def __setitem__(self, query_filter, value): + """Add a new filter by setting it on all subqueries. + + If any of the setting operations raise an exception, the ones + that succeeded are undone and the exception is propagated + upward. + + Args: + query_filter: a string of the form "property operand". + value: the value that the given property is compared against. + """ + saved_items = [] + for index, query in enumerate(self.__bound_queries): + saved_items.append(query.get(query_filter, None)) + try: + query[query_filter] = value + except: + for q, old_value in itertools.izip(self.__bound_queries[:index], + saved_items): + if old_value is not None: + q[query_filter] = old_value + else: + del q[query_filter] + raise + + def __delitem__(self, query_filter): + """Delete a filter by deleting it from all subqueries. + + If a KeyError is raised during the attempt, it is ignored, unless + every subquery raised a KeyError. If any other exception is + raised, any deletes will be rolled back. + + Args: + query_filter: the filter to delete. + + Raises: + KeyError: No subquery had an entry containing query_filter. + """ + subquery_count = len(self.__bound_queries) + keyerror_count = 0 + saved_items = [] + for index, query in enumerate(self.__bound_queries): + try: + saved_items.append(query.get(query_filter, None)) + del query[query_filter] + except KeyError: + keyerror_count += 1 + except: + for q, old_value in itertools.izip(self.__bound_queries[:index], + saved_items): + if old_value is not None: + q[query_filter] = old_value + raise + + if keyerror_count == subquery_count: + raise KeyError(query_filter) + + def __iter__(self): + return iter(self.__bound_queries) + + + +class Iterator(object): + """An iterator over the results of a datastore query. + + Iterators are used to access the results of a Query. An iterator is + obtained by building a Query, then calling Run() on it. + + Iterator implements Python's iterator protocol, so results can be accessed + with the for and in statements: + + > it = Query('Person').Run() + > for person in it: + > print 'Hi, %s!' % person['name'] + """ + def __init__(self, query_result_pb, batch_size=None, rpc=None, + query_request_pb=None): + """Constructor. + + kwargs gets stored and passed on to Next calls made by this iterator. + """ + self.__cursor = query_result_pb.cursor() + self.__keys_only = query_result_pb.keys_only() + self.__batch_size = batch_size + self.__rpc = rpc + self.__skipped_results = 0 + + self.__results_since_prev = 0 + self.__prev_compiled_cursor = None + self.__next_compiled_cursor = None + + if query_request_pb: + self.__remaining_offset = query_request_pb.offset() + else: + self.__remaining_offset = 0 + + if query_request_pb and query_result_pb.has_compiled_cursor(): + if query_request_pb.has_compiled_cursor(): + self.__next_compiled_cursor = query_request_pb.compiled_cursor() + else: + self.__next_compiled_cursor = datastore_pb.CompiledCursor() + self.__buffer = self._ProcessQueryResult(query_result_pb) + self.__results_since_prev = query_request_pb.offset() + else: + self.__buffer = self._ProcessQueryResult(query_result_pb) + + def _Get(self, count): + """Gets the next count result(s) of the query. + + Not intended to be used by application developers. Use the python + iterator protocol instead. + + This method uses _Next to returns the next entities or keys from the list of + matching results. If the query specified a sort order, results are returned + in that order. Otherwise, the order is undefined. + + The argument, count, specifies the number of results to return. However, the + length of the returned list may be smaller than count. This is the case only + if count is greater than the number of remaining results. + + The results are always returned as a list. If there are no results left, + an empty list is returned. + + Args: + # the number of results to return; must be >= 1 + count: int or long + + Returns: + # a list of entities or keys + [Entity or Key, ...] + """ + entity_list = self._Next(count) + while len(entity_list) < count and self.__more_results: + entity_list += self._Next(count - len(entity_list)) + return entity_list; + + def _Next(self, count=None): + """Returns the next batch of results. + + Not intended to be used by application developers. Use the python + iterator protocol instead. + + Values are returned in the order they are recieved from the datastore. + + If there are values in the internal buffer they are returned, otherwise a + single RPC is run in an attempt to fulfill the request. + + The optional argument, count, specifies the number of results to return. + However, the length of the returned list may be smaller than count. This is + the case if: + - the local buffer has results and count is greater than the number of + results in the buffer. + - count is greater than the number of remaining results + - the size of the remaining results exceeds the RPC buffer limit + Use _Get to ensure all possible entities are retrieved. + + When count is None, if there are items in the local buffer, they are + all returned, otherwise the datastore backend is allowed to decide how many + entities to send. + + The internal buffer is also used by the next() method so it is best not to + mix _Next() and next(). + + The results are always returned as a list. If there are results left, at + least one result will be returned in this list. If there are no results + left, an empty list is returned. + + Args: + # the number of results to return; must be >= 1 + count: int or long or None + + Returns: + # a list of entities or keys + [Entity or Key, ...] + """ + if count is not None and (not isinstance(count, (int, long)) or count < 0): + raise datastore_errors.BadArgumentError( + 'Argument to _Next must be an int greater than or equal to 0; received ' + '%s (a %s)' % (count, typename(count))) + + if self.__buffer: + if count is None: + entity_list = self.__buffer + self.__buffer = [] + elif count <= len(self.__buffer): + entity_list = self.__buffer[:count] + del self.__buffer[:count] + else: + entity_list = self.__buffer + self.__buffer = [] + self.__results_since_prev += len(entity_list) + return entity_list + + + if not self.__more_results: + return [] + + req = datastore_pb.NextRequest() + if self.__remaining_offset: + req.set_offset(self.__remaining_offset) + if count is not None: + req.set_count(count) + if self.__next_compiled_cursor: + req.set_compile(True) + req.mutable_cursor().CopyFrom(self.__cursor) + try: + rpc = self.__rpc + if rpc: + self.__rpc = rpc.clone() + + result = _MakeSyncCall('datastore_v3', 'Next', req, + datastore_pb.QueryResult(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + + new_batch = self._ProcessQueryResult(result) + if not self.__has_advanced: + self.__more_results = False + return new_batch + + def _ProcessQueryResult(self, result): + """Returns all results from datastore_pb.QueryResult and updates + self.__more_results + + Not intended to be used by application developers. Use the python + iterator protocol instead. + + The results are always returned as a list. If there are no results left, + an empty list is returned. + + Args: + # the instance of datastore_pb.QueryResult to be stored + result: datastore_pb.QueryResult + + Returns: + # a list of entities or keys + [Entity or Key, ...] + """ + if self.__next_compiled_cursor and result.has_compiled_cursor(): + self.__prev_compiled_cursor = self.__next_compiled_cursor + self.__next_compiled_cursor = result.compiled_cursor() + self.__results_since_prev = 0 + + self.__more_results = result.more_results() + if result.skipped_results(): + self.__has_advanced = True + self.__skipped_results += result.skipped_results() + self.__remaining_offset -= result.skipped_results() + else: + self.__has_advanced = result.result_size() > 0 + + if self.__keys_only: + return [Key._FromPb(e.key()) for e in result.result_list()] + else: + return [Entity._FromPb(e) for e in result.result_list()] + + def _SkippedResults(self): + self.__PrepBuffer() + return self.__skipped_results + + def GetCompiledCursor(self, query): + if not self.__buffer: + return self.__next_compiled_cursor + elif not self.__results_since_prev: + return self.__prev_compiled_cursor + elif self.__prev_compiled_cursor: + request = query._ToPb(limit=1, offset=self.__results_since_prev, count=0) + request.mutable_compiled_cursor().CopyFrom(self.__prev_compiled_cursor) + rpc = self.__rpc + if rpc: + self.__rpc = rpc.clone() + try: + result = _MakeSyncCall('datastore_v3', 'RunQuery', request, + datastore_pb.QueryResult(), rpc) + except apiproxy_errors.ApplicationError, err: + raise _ToDatastoreError(err) + return result.compiled_cursor() + else: + return None + + def next(self): + self.__PrepBuffer() + try: + result = self.__buffer.pop(0) + except IndexError: + raise StopIteration + self.__results_since_prev += 1 + return result + + def __PrepBuffer(self): + """Loads the next set of values into the local buffer if needed.""" + while not self.__buffer and self.__more_results: + self.__buffer = self._Next(self.__batch_size) + + def __iter__(self): return self + +class _Transaction(object): + """Encapsulates a transaction currently in progress. + + If we've sent a BeginTransaction call, then handle will be a + datastore_pb.Transaction that holds the transaction handle. + + If we know the entity group for this transaction, it's stored in the + entity_group attribute, which is set by RunInTransaction(). + + modified_keys is a set containing the Keys of all entities modified (ie put + or deleted) in this transaction. If an entity is modified more than once, a + BadRequestError is raised. + """ + def __init__(self): + """Initializes modified_keys to the empty set.""" + self.handle = None + self.entity_group = None + self.modified_keys = None + self.modified_keys = set() + + +def RunInTransaction(function, *args, **kwargs): + """Runs a function inside a datastore transaction. + + Runs the user-provided function inside transaction, retries default + number of times. + + Args: + # a function to be run inside the transaction + function: callable + # positional arguments to pass to the function + args: variable number of any type + + Returns: + the function's return value, if any + + Raises: + TransactionFailedError, if the transaction could not be committed. + """ + return RunInTransactionCustomRetries( + DEFAULT_TRANSACTION_RETRIES, function, *args, **kwargs) + + +def RunInTransactionCustomRetries(retries, function, *args, **kwargs): + """Runs a function inside a datastore transaction. + + Runs the user-provided function inside a full-featured, ACID datastore + transaction. Every Put, Get, and Delete call in the function is made within + the transaction. All entities involved in these calls must belong to the + same entity group. Queries are not supported. + + The trailing arguments are passed to the function as positional arguments. + If the function returns a value, that value will be returned by + RunInTransaction. Otherwise, it will return None. + + The function may raise any exception to roll back the transaction instead of + committing it. If this happens, the transaction will be rolled back and the + exception will be re-raised up to RunInTransaction's caller. + + If you want to roll back intentionally, but don't have an appropriate + exception to raise, you can raise an instance of datastore_errors.Rollback. + It will cause a rollback, but will *not* be re-raised up to the caller. + + The function may be run more than once, so it should be idempotent. It + should avoid side effects, and it shouldn't have *any* side effects that + aren't safe to occur multiple times. This includes modifying the arguments, + since they persist across invocations of the function. However, this doesn't + include Put, Get, and Delete calls, of course. + + Example usage: + + > def decrement(key, amount=1): + > counter = datastore.Get(key) + > counter['count'] -= amount + > if counter['count'] < 0: # don't let the counter go negative + > raise datastore_errors.Rollback() + > datastore.Put(counter) + > + > counter = datastore.Query('Counter', {'name': 'foo'}) + > datastore.RunInTransaction(decrement, counter.key(), amount=5) + + Transactions satisfy the traditional ACID properties. They are: + + - Atomic. All of a transaction's operations are executed or none of them are. + + - Consistent. The datastore's state is consistent before and after a + transaction, whether it committed or rolled back. Invariants such as + "every entity has a primary key" are preserved. + + - Isolated. Transactions operate on a snapshot of the datastore. Other + datastore operations do not see intermediated effects of the transaction; + they only see its effects after it has committed. + + - Durable. On commit, all writes are persisted to the datastore. + + Nested transactions are not supported. + + Args: + # number of retries + retries: integer + # a function to be run inside the transaction + function: callable + # positional arguments to pass to the function + args: variable number of any type + + Returns: + the function's return value, if any + + Raises: + TransactionFailedError, if the transaction could not be committed. + """ + + if _CurrentTransactionKey(): + raise datastore_errors.BadRequestError( + 'Nested transactions are not supported.') + + if retries < 0: + raise datastore_errors.BadRequestError( + 'Number of retries should be non-negative number.') + + tx_key = None + + try: + tx_key = _NewTransactionKey() + tx = _Transaction() + _txes[tx_key] = tx + + for i in range(0, retries + 1): + tx.modified_keys.clear() + + try: + result = function(*args, **kwargs) + except: + original_exception = sys.exc_info() + + if tx.handle: + try: + _MakeSyncCall('datastore_v3', 'Rollback', + tx.handle, api_base_pb.VoidProto()) + except: + logging.info('Exception sending Rollback:\n' + + traceback.format_exc()) + + type, value, trace = original_exception + if type is datastore_errors.Rollback: + return + else: + raise type, value, trace + + if tx.handle: + try: + _MakeSyncCall('datastore_v3', 'Commit', + tx.handle, datastore_pb.CommitResponse()) + except apiproxy_errors.ApplicationError, err: + if (err.application_error == + datastore_pb.Error.CONCURRENT_TRANSACTION): + logging.warning('Transaction collision for entity group with ' + 'key %r. Retrying...', tx.entity_group) + tx.handle = None + tx.entity_group = None + continue + else: + raise _ToDatastoreError(err) + + return result + + raise datastore_errors.TransactionFailedError( + 'The transaction could not be committed. Please try again.') + + finally: + if tx_key in _txes: + del _txes[tx_key] + del tx_key + + +def _MaybeSetupTransaction(request, keys): + """Begins a transaction, if necessary, and populates it in the request. + + If we're currently inside a transaction, this records the entity group, + checks that the keys are all in that entity group, creates the transaction + PB, and sends the BeginTransaction. It then populates the transaction handle + in the request. + + Raises BadRequestError if the entity has a different entity group than the + current transaction. + + Args: + request: GetRequest, PutRequest, DeleteRequest, or Query + keys: sequence of Keys + + Returns: + _Transaction if we're inside a transaction, otherwise None + """ + assert isinstance(request, (datastore_pb.GetRequest, datastore_pb.PutRequest, + datastore_pb.DeleteRequest, datastore_pb.Query, + taskqueue_service_pb.TaskQueueAddRequest, + )), request.__class__ + tx_key = None + + try: + tx_key = _CurrentTransactionKey() + if tx_key: + tx = _txes[tx_key] + + groups = [k.entity_group() for k in keys] + if tx.entity_group: + expected_group = tx.entity_group + elif groups: + expected_group = groups[0] + else: + expected_group = None + + for group in groups: + if (group != expected_group or + + + + + + + + (not group.has_id_or_name() and group is not expected_group)): + raise _DifferentEntityGroupError(expected_group, group) + + if not tx.entity_group and group.has_id_or_name(): + tx.entity_group = group + + if not tx.handle: + req = datastore_pb.BeginTransactionRequest() + if keys: + req.set_app(keys[0].app()) + else: + assert isinstance(request, taskqueue_service_pb.TaskQueueAddRequest) + req.set_app(os.environ['APPLICATION_ID']) + assert req.app() + + tx.handle = _MakeSyncCall('datastore_v3', 'BeginTransaction', + req, datastore_pb.Transaction()) + + if not tx.handle.app(): + tx.handle.set_app(req.app()) + + request.mutable_transaction().CopyFrom(tx.handle) + + return tx + + finally: + del tx_key + + +def IsInTransaction(): + """Determine whether already running in transaction. + + Returns: + True if already running in transaction, else False. + """ + return bool(_CurrentTransactionKey()) + + +def _DifferentEntityGroupError(a, b): + """Raises a BadRequestError that says the given entity groups are different. + + Includes the two entity groups in the message, formatted more clearly and + concisely than repr(Key). + + Args: + a, b are both Keys that represent entity groups. + """ + def id_or_name(key): + if key.name(): + return 'name=%r' % key.name() + else: + return 'id=%r' % key.id() + + raise datastore_errors.BadRequestError( + 'Cannot operate on different entity groups in a transaction: ' + '(kind=%r, %s) and (kind=%r, %s).' % (a.kind(), id_or_name(a), + b.kind(), id_or_name(b))) + + +def _FindTransactionFrameInStack(): + """Walks the stack to find a RunInTransaction() call. + + Returns: + # this is the RunInTransactionCustomRetries() frame record, if found + frame record or None + """ + frame = sys._getframe() + filename = frame.f_code.co_filename + + frame = frame.f_back.f_back + while frame: + if (frame.f_code.co_filename == filename and + frame.f_code.co_name == 'RunInTransactionCustomRetries'): + return frame + frame = frame.f_back + + return None + +_CurrentTransactionKey = _FindTransactionFrameInStack + +_NewTransactionKey = sys._getframe + + +def _GetCompleteKeyOrError(arg): + """Expects an Entity or a Key, and returns the corresponding Key. Raises + BadArgumentError or BadKeyError if arg is a different type or is incomplete. + + Args: + arg: Entity or Key + + Returns: + Key + """ + if isinstance(arg, Key): + key = arg + elif isinstance(arg, basestring): + key = Key(arg) + elif isinstance(arg, Entity): + key = arg.key() + elif not isinstance(arg, Key): + raise datastore_errors.BadArgumentError( + 'Expects argument to be an Entity or Key; received %s (a %s).' % + (arg, typename(arg))) + assert isinstance(key, Key) + + if not key.has_id_or_name(): + raise datastore_errors.BadKeyError('Key %r is not complete.' % key) + + return key + + +def _GetPropertyValue(entity, property): + """Returns an entity's value for a given property name. + + Handles special properties like __key__ as well as normal properties. + + Args: + entity: datastore.Entity + property: str; the property name + + Returns: + property value. For __key__, a datastore_types.Key. + + Raises: + KeyError, if the entity does not have the given property. + """ + if property in datastore_types._SPECIAL_PROPERTIES: + if property == datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY: + raise KeyError(property) + assert property == datastore_types._KEY_SPECIAL_PROPERTY + return entity.key() + else: + return entity[property] + + +def _AddOrAppend(dictionary, key, value): + """Adds the value to the existing values in the dictionary, if any. + + If dictionary[key] doesn't exist, sets dictionary[key] to value. + + If dictionary[key] is not a list, sets dictionary[key] to [old_value, value]. + + If dictionary[key] is a list, appends value to that list. + + Args: + dictionary: a dict + key, value: anything + """ + if key in dictionary: + existing_value = dictionary[key] + if isinstance(existing_value, list): + existing_value.append(value) + else: + dictionary[key] = [existing_value, value] + else: + dictionary[key] = value + + +def _ToDatastoreError(err): + """Converts an apiproxy.ApplicationError to an error in datastore_errors. + + Args: + err: apiproxy.ApplicationError + + Returns: + a subclass of datastore_errors.Error + """ + return _DatastoreExceptionFromErrorCodeAndDetail(err.application_error, + err.error_detail) + + +def _DatastoreExceptionFromErrorCodeAndDetail(error, detail): + """Converts a datastore_pb.Error into a datastore_errors.Error. + + Args: + error: A member of the datastore_pb.Error enumeration. + detail: A string providing extra details about the error. + + Returns: + A subclass of datastore_errors.Error. + """ + exception_class = { + datastore_pb.Error.BAD_REQUEST: datastore_errors.BadRequestError, + datastore_pb.Error.CONCURRENT_TRANSACTION: + datastore_errors.TransactionFailedError, + datastore_pb.Error.INTERNAL_ERROR: datastore_errors.InternalError, + datastore_pb.Error.NEED_INDEX: datastore_errors.NeedIndexError, + datastore_pb.Error.TIMEOUT: datastore_errors.Timeout, + datastore_pb.Error.BIGTABLE_ERROR: datastore_errors.Timeout, + datastore_pb.Error.COMMITTED_BUT_STILL_APPLYING: + datastore_errors.CommittedButStillApplying, + datastore_pb.Error.CAPABILITY_DISABLED: + apiproxy_errors.CapabilityDisabledError, + }.get(error, datastore_errors.Error) + + if detail is None: + return exception_class() + else: + return exception_class(detail) diff --git a/google_appengine/google/appengine/api/datastore.pyc b/google_appengine/google/appengine/api/datastore.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f54c555214aad5891711539b102f52fe0bd63624 GIT binary patch literal 87371 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 Q`<9x#`CfCmzEX$J-)7K|`v3p{ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/datastore_entities.py b/google_appengine/google/appengine/api/datastore_entities.py new file mode 100755 index 0000000..93ffdb5 --- /dev/null +++ b/google_appengine/google/appengine/api/datastore_entities.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Classes for common kinds, including Contact, Message, and Event. + +Most of these kinds are based on the gd namespace "kinds" from GData: + + http://code.google.com/apis/gdata/common-elements.html +""" + + + + + +import types +import urlparse +from xml.sax import saxutils +from google.appengine.datastore import datastore_pb +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types + +class GdKind(datastore.Entity): + """ A base class for gd namespace kinds. + + This class contains common logic for all gd namespace kinds. For example, + this class translates datastore (app id, kind, key) tuples to tag: + URIs appropriate for use in tags. + """ + + HEADER = u""" + """ + FOOTER = u""" +""" + + _kind_properties = set() + _contact_properties = set() + + def __init__(self, kind, title, kind_properties, contact_properties=[]): + """ Ctor. + + title is the name of this particular entity, e.g. Bob Jones or Mom's + Birthday Party. + + kind_properties is a list of property names that should be included in + this entity's XML encoding as first-class XML elements, instead of + elements. 'title' and 'content' are added to kind_properties + automatically, and may not appear in contact_properties. + + contact_properties is a list of property names that are Keys that point to + Contact entities, and should be included in this entity's XML encoding as + elements. If a property name is included in both kind_properties + and contact_properties, it is treated as a Contact property. + + Args: + kind: string + title: string + kind_properties: list of strings + contact_properties: list of string + """ + datastore.Entity.__init__(self, kind) + + if not isinstance(title, types.StringTypes): + raise datastore_errors.BadValueError( + 'Expected a string for title; received %s (a %s).' % + (title, datastore_types.typename(title))) + self['title'] = title + self['content'] = '' + + self._contact_properties = set(contact_properties) + assert not self._contact_properties.intersection(self.keys()) + + self._kind_properties = set(kind_properties) - self._contact_properties + self._kind_properties.add('title') + self._kind_properties.add('content') + + def _KindPropertiesToXml(self): + """ Convert the properties that are part of this gd kind to XML. For + testability, the XML elements in the output are sorted alphabetically + by property name. + + Returns: + string # the XML representation of the gd kind properties + """ + properties = self._kind_properties.intersection(set(self.keys())) + + xml = u'' + for prop in sorted(properties): + prop_xml = saxutils.quoteattr(prop)[1:-1] + + value = self[prop] + has_toxml = (hasattr(value, 'ToXml') or + isinstance(value, list) and hasattr(value[0], 'ToXml')) + + for val in self._XmlEscapeValues(prop): + if has_toxml: + xml += '\n %s' % val + else: + xml += '\n <%s>%s' % (prop_xml, val, prop_xml) + + return xml + + + def _ContactPropertiesToXml(self): + """ Convert this kind's Contact properties kind to XML. For testability, + the XML elements in the output are sorted alphabetically by property name. + + Returns: + string # the XML representation of the Contact properties + """ + properties = self._contact_properties.intersection(set(self.keys())) + + xml = u'' + for prop in sorted(properties): + values = self[prop] + if not isinstance(values, list): + values = [values] + + for value in values: + assert isinstance(value, datastore_types.Key) + xml += """ + + """ % (self.kind().lower(), prop, value.ToTagUri()) + + return xml + + + def _LeftoverPropertiesToXml(self): + """ Convert all of this entity's properties that *aren't* part of this gd + kind to XML. + + Returns: + string # the XML representation of the leftover properties + """ + leftovers = set(self.keys()) + leftovers -= self._kind_properties + leftovers -= self._contact_properties + if leftovers: + return u'\n ' + '\n '.join(self._PropertiesToXml(leftovers)) + else: + return u'' + + def ToXml(self): + """ Returns an XML representation of this entity, as a string. + """ + xml = GdKind.HEADER % self.kind().lower() + xml += self._KindPropertiesToXml() + xml += self._ContactPropertiesToXml() + xml += self._LeftoverPropertiesToXml() + xml += GdKind.FOOTER + return xml + + +class Message(GdKind): + """A message, such as an email, a discussion group posting, or a comment. + + Includes the message title, contents, participants, and other properties. + + This is the gd Message kind. See: + http://code.google.com/apis/gdata/common-elements.html#gdMessageKind + + These properties are meaningful. They are all optional. + + property name property type meaning + ------------------------------------- + title string message subject + content string message body + from Contact* sender + to Contact* primary recipient + cc Contact* CC recipient + bcc Contact* BCC recipient + reply-to Contact* intended recipient of replies + link Link* attachment + category Category* tag or label associated with this message + geoPt GeoPt* geographic location the message was posted from + rating Rating* message rating, as defined by the application + + * means this property may be repeated. + + The Contact properties should be Keys of Contact entities. They are + represented in the XML encoding as linked elements. + """ + KIND_PROPERTIES = ['title', 'content', 'link', 'category', 'geoPt', 'rating'] + CONTACT_PROPERTIES = ['from', 'to', 'cc', 'bcc', 'reply-to'] + + def __init__(self, title, kind='Message'): + GdKind.__init__(self, kind, title, Message.KIND_PROPERTIES, + Message.CONTACT_PROPERTIES) + + +class Event(GdKind): + """A calendar event. + + Includes the event title, description, location, organizer, start and end + time, and other details. + + This is the gd Event kind. See: + http://code.google.com/apis/gdata/common-elements.html#gdEventKind + + These properties are meaningful. They are all optional. + + property name property type meaning + ------------------------------------- + title string event name + content string event description + author string the organizer's name + where string* human-readable location (not a GeoPt) + startTime timestamp start time + endTime timestamp end time + eventStatus string one of the Event.Status values + link Link* page with more information + category Category* tag or label associated with this event + attendee Contact* attendees and other related people + + * means this property may be repeated. + + The Contact properties should be Keys of Contact entities. They are + represented in the XML encoding as linked elements. + """ + KIND_PROPERTIES = ['title', 'content', 'author', 'where', 'startTime', + 'endTime', 'eventStatus', 'link', 'category'] + CONTACT_PROPERTIES = ['attendee'] + + class Status: + CONFIRMED = 'confirmed' + TENTATIVE = 'tentative' + CANCELED = 'canceled' + + def __init__(self, title, kind='Event'): + GdKind.__init__(self, kind, title, Event.KIND_PROPERTIES, + Event.CONTACT_PROPERTIES) + + def ToXml(self): + """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and + gd:eventStatus. + """ + xml = GdKind.HEADER % self.kind().lower() + + self._kind_properties = set(Contact.KIND_PROPERTIES) + xml += self._KindPropertiesToXml() + + if 'author' in self: + xml += """ + %s""" % self['author'] + + if 'eventStatus' in self: + xml += """ + """ % ( + self['eventStatus']) + + if 'where' in self: + lines = ['' % val + for val in self._XmlEscapeValues('where')] + xml += '\n ' + '\n '.join(lines) + + iso_format = '%Y-%m-%dT%H:%M:%S' + xml += '\n + element; see the reference section for that element for details. + + These properties are meaningful. They are all optional. + + property name property type meaning + ------------------------------------- + title string contact's name + content string notes + email Email* email address + geoPt GeoPt* geographic location + im IM* IM address + phoneNumber Phonenumber* phone number + postalAddress PostalAddress* mailing address + link Link* link to more information + category Category* tag or label associated with this contact + + * means this property may be repeated. + """ + CONTACT_SECTION_HEADER = """ + """ + CONTACT_SECTION_FOOTER = """ + """ + + KIND_PROPERTIES = ['title', 'content', 'link', 'category'] + + CONTACT_SECTION_PROPERTIES = ['email', 'geoPt', 'im', 'phoneNumber', + 'postalAddress'] + + def __init__(self, title, kind='Contact'): + GdKind.__init__(self, kind, title, Contact.KIND_PROPERTIES) + + def ToXml(self): + """ Override GdKind.ToXml() to put some properties inside a + gd:contactSection. + """ + xml = GdKind.HEADER % self.kind().lower() + + self._kind_properties = set(Contact.KIND_PROPERTIES) + xml += self._KindPropertiesToXml() + + xml += Contact.CONTACT_SECTION_HEADER + self._kind_properties = set(Contact.CONTACT_SECTION_PROPERTIES) + xml += self._KindPropertiesToXml() + xml += Contact.CONTACT_SECTION_FOOTER + + self._kind_properties.update(Contact.KIND_PROPERTIES) + xml += self._LeftoverPropertiesToXml() + xml += GdKind.FOOTER + return xml diff --git a/google_appengine/google/appengine/api/datastore_errors.py b/google_appengine/google/appengine/api/datastore_errors.py new file mode 100755 index 0000000..f1acdf3 --- /dev/null +++ b/google_appengine/google/appengine/api/datastore_errors.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Errors used in the Python datastore API.""" + + + + + + +class Error(Exception): + """Base datastore error type. + """ + +class BadValueError(Error): + """Raised by Entity.__setitem__(), Query.__setitem__(), Get(), and others + when a property value or filter value is invalid. + """ + +class BadPropertyError(Error): + """Raised by Entity.__setitem__() when a property name isn't a string. + """ + +class BadRequestError(Error): + """Raised by datastore calls when the parameter(s) are invalid. + """ + +class EntityNotFoundError(Error): + """DEPRECATED: Raised by Get() when the requested entity is not found. + """ + +class BadArgumentError(Error): + """Raised by Query.Order(), Iterator.Next(), and others when they're + passed an invalid argument. + """ + +class QueryNotFoundError(Error): + """DEPRECATED: Raised by Iterator methods when the Iterator is invalid. This + should not happen during normal usage; it protects against malicious users + and system errors. + """ + +class TransactionNotFoundError(Error): + """DEPRECATED: Raised by RunInTransaction. This is an internal error; you + should not see this. + """ + +class Rollback(Error): + """May be raised by transaction functions when they want to roll back + instead of committing. Note that *any* exception raised by a transaction + function will cause a rollback. This is purely for convenience. See + datastore.RunInTransaction for details. + """ + +class TransactionFailedError(Error): + """Raised by RunInTransaction methods when the transaction could not be + committed, even after retrying. This is usually due to high contention. + """ + +class BadFilterError(Error): + """Raised by Query.__setitem__() and Query.Run() when a filter string is + invalid. + """ + def __init__(self, filter): + self.filter = filter + + def __str__(self): + return (u'BadFilterError: invalid filter: %s.' % self.filter) + +class BadQueryError(Error): + """Raised by Query when a query or query string is invalid. + """ + +class BadKeyError(Error): + """Raised by Key.__str__ when the key is invalid. + """ + +class InternalError(Error): + """An internal datastore error. Please report this to Google. + """ + +class NeedIndexError(Error): + """No matching index was found for a query that requires an index. Check + the Indexes page in the Admin Console and your index.yaml file. + """ + +class Timeout(Error): + """The datastore operation timed out, or the data was temporarily + unavailable. This can happen when you attempt to put, get, or delete too + many entities or an entity with too many properties, or if the datastore is + overloaded or having trouble. + """ + +class CommittedButStillApplying(Timeout): + """The write or transaction was committed, but some entities or index rows + may not have been fully updated. Those updates should automatically be + applied soon. You can roll them forward immediately by reading one of the + entities inside a transaction. + """ diff --git a/google_appengine/google/appengine/api/datastore_errors.pyc b/google_appengine/google/appengine/api/datastore_errors.pyc new file mode 100644 index 0000000000000000000000000000000000000000..517efe086d7fc04cd5910565e7fe9cd0cac14492 GIT binary patch literal 6633 zcwWs|ZEqAe5ccKrLJ~pEghNBUDL^{@0}KXqny*SATE$|1pV()xC1y<^Wa+glRKQGE}16KU% zaD4?=@^g(b$=D-kYfRPHUX95K_A6t*F!u1#1e0~9CP+HzrFAB!O6jzhPBJ-DN{@Nz z6qB>1bk0ksnVc`B$Gvoh$%RsS!b^`axmZe1dg&~aOQrObm(DSHx|E*r(s?G&meOS} zJ3d#!lF9c=DfiMPCO=?miKLgk^c0g< z*iPd^^h5)ib!&}Hc$z55b;Mmy@$Jm@OvI&dB5}qlzPi2HbW>$#?@{n0yMua8K%>B; zHS+5OYwa}fU2+LKh(uLXDb&DS7OUnQ=M4-BcMO_#I}&}>Zo4^p=o^`a_&7t4(gf`` z&Xx_Egq@pf|a2_#CpX~s!fwhnsL@b z#TQWd1T@nVZhwZjT)V*^q{EuBD(58nmDVY?l|$lg>%{CG2s?-JX-2$suDLM z9A6%QW4NvY5r#>Q1TNuNSO5Vcqmf+W0^Yv`aWM@#Kd#_#8vb&BZW*^>(nyYo1%}4m z_3hUB+UoB5-J5)f3oW4`8a9U--YFUn7fNIt-@`%22?=Jm>lH|frLt~D1|le*&^WQeU83o+s=HJ0 zM>f7<^!&kI6g?$W99B@z$jY+vZz`FV@AfpN=%i=TP*Pm=$QmwFOg$j6eG$SAE4u0y z*A5evQ-Mpk=n5Srj>BsmXp_=5O!*$2lVlLGeO@3iM&xHf=cgX|Np~LpcCCmK5jbt4 zSH%DE3-NEI(PmT`Acq(+r;r36M1UvF?-tKYdJz0XDe#pZD`H4uK4ZgJ#n^3`Fzkrn z$;%^zJ-!ne??49g;J6_!d@qgYrm}+hKtzr^!z~QQaXf$lgG>on?t45i{l0b%w?qz( zL#Tw~*F}_F=jvIYVp2RDPE-d3RppR;ph15iAYh@7Eo>WlDo(8mGsv(OhK%-Aq*WBC zCf`w*VLWEd!@j03q;f)sWA)%X=)CPcIOUevftcTb`KeJO{&!Z*&qpjjl5MK44t#uf z5WvEXO5WgVAKA|yvOuex&B(Q-8`2~NVgN(|*dYW(Pj`Fh4~P38EUYmCd>nMHv*8j! z&|vG^AU^v7VEG|np?-w228%eX0K=P55 z?K0*W?R<{FH)>S-KRIAOeBUgbKFp6#RQ8rwjit;F6|2$jn*~uB(e5vxS*_5VLq{h~ zz8xw&Qn4yF))CXe%zlriI!+AEfX*K)7@&b$O3BSgs%ImD@Ed+>fET-l%s8>CL!&4GLq{4wRV1=o>f!V|ry$EO6CLz9r z)nNt*q>KRj80@8Uw2OKsEC+T!TZ< zk5O5b547uHue?ujoCCc`iQe-Q%*~*^+3YKeFryh3CbW;y;;czK<8|F~?hQ{L@l0{8 z73J!_ z0$QoCpg^*i#$b}7%GN-p$sd!l#uBMBurPH%#FE%3;1OzYL1M62dkCR`;XxG$_^gl1 zbs((dx)1TDK^cCS!J-us{beGBr3rKeU4FJuv}lRNMa9N(y7V0A42Le=YMd@dcUT~= zm&FsEA=4o!o&3?U7ajD_2?iC@R3B1_K{S}C1MQr&kx(Y3#ZO^G1HS3 0 and limit < len(results): + results = results[:limit] + + self.__results = results + self.__query = query + self.__offset = 0 + + self.app = query.app() + self.keys_only = query.keys_only() + self.count = len(self.__results) + self.cursor = self._AcquireCursorID() + + def _AcquireCursorID(self): + """Acquires the next cursor id in a thread safe manner. + """ + self._next_cursor_lock.acquire() + try: + cursor_id = _Cursor._next_cursor + _Cursor._next_cursor += 1 + finally: + self._next_cursor_lock.release() + return cursor_id + + @staticmethod + def _GetCursorOffset(results, cursor_entity, inclusive, compare): + """Converts a cursor entity into a offset into the result set even if the + cursor_entity no longer exists. + + Args: + cursor_entity: the decoded datastore.Entity from the compiled query + inclusive: boolean that specifies if to offset past the cursor_entity + compare: a function that takes two datastore.Entity and compares them + Returns: + the integer offset + """ + lo = 0 + hi = len(results) + if inclusive: + while lo < hi: + mid = (lo + hi) // 2 + if compare(results[mid], cursor_entity) < 0: + lo = mid + 1 + else: + hi = mid + else: + while lo < hi: + mid = (lo + hi) // 2 + if compare(cursor_entity, results[mid]) < 0: + hi = mid + else: + lo = mid + 1 + return lo + + def _ValidateQuery(self, query, query_info): + """Ensure that the given query matches the query_info. + + Args: + query: datastore_pb.Query instance we are chacking + query_info: datastore_pb.Query instance we want to match + + Raises BadRequestError on failure. + """ + error_msg = 'Cursor does not match query: %s' + exc = datastore_errors.BadRequestError + if query_info.filter_list() != query.filter_list(): + raise exc(error_msg % 'filters do not match') + if query_info.order_list() != query.order_list(): + raise exc(error_msg % 'orders do not match') + + for attr in ('ancestor', 'kind', 'name_space', 'search_query'): + query_info_has_attr = getattr(query_info, 'has_%s' % attr) + query_info_attr = getattr(query_info, attr) + query_has_attr = getattr(query, 'has_%s' % attr) + query_attr = getattr(query, attr) + if query_info_has_attr(): + if not query_has_attr() or query_info_attr() != query_attr(): + raise exc(error_msg % ('%s does not match' % attr)) + elif query_has_attr(): + raise exc(error_msg % ('%s does not match' % attr)) + + def _MinimalQueryInfo(self, query): + """Extract the minimal set of information for query matching. + + Args: + query: datastore_pb.Query instance from which to extract info. + + Returns: + datastore_pb.Query instance suitable for matching against when + validating cursors. + """ + query_info = datastore_pb.Query() + query_info.set_app(query.app()) + + for filter in query.filter_list(): + query_info.filter_list().append(filter) + for order in query.order_list(): + query_info.order_list().append(order) + + if query.has_ancestor(): + query_info.mutable_ancestor().CopyFrom(query.ancestor()) + + for attr in ('kind', 'name_space', 'search_query'): + query_has_attr = getattr(query, 'has_%s' % attr) + query_attr = getattr(query, attr) + query_info_set_attr = getattr(query_info, 'set_%s' % attr) + if query_has_attr(): + query_info_set_attr(query_attr()) + + return query_info + + def _MinimalEntityInfo(self, entity_proto, query): + """Extract the minimal set of information that preserves entity order. + + Args: + entity_proto: datastore_pb.EntityProto instance from which to extract + information + query: datastore_pb.Query instance for which ordering must be preserved. + + Returns: + datastore_pb.EntityProto instance suitable for matching against a list of + results when finding cursor positions. + """ + entity_info = datastore_pb.EntityProto(); + order_names = [o.property() for o in query.order_list()] + entity_info.mutable_key().MergeFrom(entity_proto.key()) + entity_info.mutable_entity_group().MergeFrom(entity_proto.entity_group()) + for prop in entity_proto.property_list(): + if prop.name() in order_names: + entity_info.add_property().MergeFrom(prop) + return entity_info; + + def _DecodeCompiledCursor(self, query, compiled_cursor): + """Converts a compiled_cursor into a cursor_entity. + + Returns: + (cursor_entity, inclusive): a datastore.Entity and if it should be + included in the result set. + """ + assert len(compiled_cursor.position_list()) == 1 + + position = compiled_cursor.position(0) + entity_pb = datastore_pb.EntityProto() + (query_info_encoded, entity_encoded) = position.start_key().split( + _CURSOR_CONCAT_STR, 1) + query_info_pb = datastore_pb.Query() + query_info_pb.ParseFromString(query_info_encoded) + self._ValidateQuery(query, query_info_pb) + + entity_pb.ParseFromString(entity_encoded) + return (datastore.Entity._FromPb(entity_pb, True), + position.start_inclusive()) + + def _EncodeCompiledCursor(self, query, compiled_cursor): + """Converts the current state of the cursor into a compiled_cursor + + Args: + query: the datastore_pb.Query this cursor is related to + compiled_cursor: an empty datstore_pb.CompiledCursor + """ + if self.__last_result is not None: + position = compiled_cursor.add_position() + query_info = self._MinimalQueryInfo(query) + entity_info = self._MinimalEntityInfo(self.__last_result.ToPb(), query) + start_key = _CURSOR_CONCAT_STR.join(( + query_info.Encode(), + entity_info.Encode())) + position.set_start_key(str(start_key)) + position.set_start_inclusive(False) + + def PopulateQueryResult(self, result, count, offset, compile=False): + """Populates a QueryResult with this cursor and the given number of results. + + Args: + result: datastore_pb.QueryResult + count: integer of how many results to return + offset: integer of how many results to skip + compile: boolean, whether we are compiling this query + """ + offset = min(offset, self.count - self.__offset) + limited_offset = min(offset, _MAX_QUERY_OFFSET) + if limited_offset: + self.__offset += limited_offset + result.set_skipped_results(limited_offset) + + if offset == limited_offset and count: + if count > _MAXIMUM_RESULTS: + count = _MAXIMUM_RESULTS + results = self.__results[self.__offset:self.__offset + count] + count = len(results) + self.__offset += count + result.result_list().extend(r._ToPb() for r in results) + + if self.__offset: + self.__last_result = self.__results[self.__offset - 1] + + result.mutable_cursor().set_app(self.app) + result.mutable_cursor().set_cursor(self.cursor) + result.set_keys_only(self.keys_only) + result.set_more_results(self.__offset < self.count) + if compile: + self._EncodeCompiledCursor( + self.__query, result.mutable_compiled_cursor()) + + +class DatastoreFileStub(apiproxy_stub.APIProxyStub): + """ Persistent stub for the Python datastore API. + + Stores all entities in memory, and persists them to a file as pickled + protocol buffers. A DatastoreFileStub instance handles a single app's data + and is backed by files on disk. + """ + + _PROPERTY_TYPE_TAGS = { + datastore_types.Blob: entity_pb.PropertyValue.kstringValue, + bool: entity_pb.PropertyValue.kbooleanValue, + datastore_types.Category: entity_pb.PropertyValue.kstringValue, + datetime.datetime: entity_pb.PropertyValue.kint64Value, + datastore_types.Email: entity_pb.PropertyValue.kstringValue, + float: entity_pb.PropertyValue.kdoubleValue, + datastore_types.GeoPt: entity_pb.PropertyValue.kPointValueGroup, + datastore_types.IM: entity_pb.PropertyValue.kstringValue, + int: entity_pb.PropertyValue.kint64Value, + datastore_types.Key: entity_pb.PropertyValue.kReferenceValueGroup, + datastore_types.Link: entity_pb.PropertyValue.kstringValue, + long: entity_pb.PropertyValue.kint64Value, + datastore_types.PhoneNumber: entity_pb.PropertyValue.kstringValue, + datastore_types.PostalAddress: entity_pb.PropertyValue.kstringValue, + datastore_types.Rating: entity_pb.PropertyValue.kint64Value, + str: entity_pb.PropertyValue.kstringValue, + datastore_types.Text: entity_pb.PropertyValue.kstringValue, + type(None): 0, + unicode: entity_pb.PropertyValue.kstringValue, + users.User: entity_pb.PropertyValue.kUserValueGroup, + } + + WRITE_ONLY = entity_pb.CompositeIndex.WRITE_ONLY + READ_WRITE = entity_pb.CompositeIndex.READ_WRITE + DELETED = entity_pb.CompositeIndex.DELETED + ERROR = entity_pb.CompositeIndex.ERROR + + _INDEX_STATE_TRANSITIONS = { + WRITE_ONLY: frozenset((READ_WRITE, DELETED, ERROR)), + READ_WRITE: frozenset((DELETED,)), + ERROR: frozenset((DELETED,)), + DELETED: frozenset((ERROR,)), + } + + def __init__(self, + app_id, + datastore_file, + history_file=None, + require_indexes=False, + service_name='datastore_v3', + trusted=False): + """Constructor. + + Initializes and loads the datastore from the backing files, if they exist. + + Args: + app_id: string + datastore_file: string, stores all entities across sessions. Use None + not to use a file. + history_file: DEPRECATED. No-op. + require_indexes: bool, default False. If True, composite indexes must + exist in index.yaml for queries that need them. + service_name: Service name expected for all calls. + trusted: bool, default False. If True, this stub allows an app to + access the data of another app. + """ + super(DatastoreFileStub, self).__init__(service_name) + + + assert isinstance(app_id, basestring) and app_id != '' + self.__app_id = app_id + self.__datastore_file = datastore_file + self.SetTrusted(trusted) + + self.__entities = {} + + self.__schema_cache = {} + + self.__tx_snapshot = {} + + self.__tx_actions = [] + + self.__queries = {} + + self.__transactions = set() + + self.__indexes = {} + self.__require_indexes = require_indexes + + self.__query_history = {} + + self.__next_id = 1 + self.__next_tx_handle = 1 + self.__next_index_id = 1 + self.__id_lock = threading.Lock() + self.__tx_handle_lock = threading.Lock() + self.__index_id_lock = threading.Lock() + self.__tx_lock = threading.Lock() + self.__entities_lock = threading.Lock() + self.__file_lock = threading.Lock() + self.__indexes_lock = threading.Lock() + + self.Read() + + def Clear(self): + """ Clears the datastore by deleting all currently stored entities and + queries. """ + self.__entities = {} + self.__queries = {} + self.__transactions = set() + self.__query_history = {} + self.__schema_cache = {} + + def SetTrusted(self, trusted): + """Set/clear the trusted bit in the stub. + + This bit indicates that the app calling the stub is trusted. A + trusted app can write to datastores of other apps. + + Args: + trusted: boolean. + """ + self.__trusted = trusted + + def __ValidateAppId(self, app_id): + """Verify that this is the stub for app_id. + + Args: + app_id: An application ID. + + Raises: + datastore_errors.BadRequestError: if this is not the stub for app_id. + """ + assert app_id + if not self.__trusted and app_id != self.__app_id: + raise datastore_errors.BadRequestError( + 'app %s cannot access app %s\'s data' % (self.__app_id, app_id)) + + def __ValidateKey(self, key): + """Validate this key. + + Args: + key: entity_pb.Reference + + Raises: + datastore_errors.BadRequestError: if the key is invalid + """ + assert isinstance(key, entity_pb.Reference) + + self.__ValidateAppId(key.app()) + + for elem in key.path().element_list(): + if elem.has_id() == elem.has_name(): + raise datastore_errors.BadRequestError( + 'each key path element should have id or name but not both: %r' % key) + + def __ValidateTransaction(self, tx): + """Verify that this transaction exists and is valid. + + Args: + tx: datastore_pb.Transaction + + Raises: + datastore_errors.BadRequestError: if the tx is valid or doesn't exist. + """ + assert isinstance(tx, datastore_pb.Transaction) + self.__ValidateAppId(tx.app()) + if tx not in self.__transactions: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Transaction %s not found' % tx) + + def _AppIdNamespaceKindForKey(self, key): + """ Get (app, kind) tuple from given key. + + The (app, kind) tuple is used as an index into several internal + dictionaries, e.g. __entities. + + Args: + key: entity_pb.Reference + + Returns: + Tuple (app, kind), both are unicode strings. + """ + last_path = key.path().element_list()[-1] + return (datastore_types.EncodeAppIdNamespace(key.app(), key.name_space()), + last_path.type()) + + def _StoreEntity(self, entity): + """ Store the given entity. + + Args: + entity: entity_pb.EntityProto + """ + key = entity.key() + app_kind = self._AppIdNamespaceKindForKey(key) + if app_kind not in self.__entities: + self.__entities[app_kind] = {} + self.__entities[app_kind][key] = _StoredEntity(entity) + + if app_kind in self.__schema_cache: + del self.__schema_cache[app_kind] + + READ_PB_EXCEPTIONS = (ProtocolBuffer.ProtocolBufferDecodeError, LookupError, + TypeError, ValueError) + READ_ERROR_MSG = ('Data in %s is corrupt or a different version. ' + 'Try running with the --clear_datastore flag.\n%r') + READ_PY250_MSG = ('Are you using FloatProperty and/or GeoPtProperty? ' + 'Unfortunately loading float values from the datastore ' + 'file does not work with Python 2.5.0. ' + 'Please upgrade to a newer Python 2.5 release or use ' + 'the --clear_datastore flag.\n') + + def Read(self): + """ Reads the datastore and history files into memory. + + The in-memory query history is cleared, but the datastore is *not* + cleared; the entities in the files are merged into the entities in memory. + If you want them to overwrite the in-memory datastore, call Clear() before + calling Read(). + + If the datastore file contains an entity with the same app name, kind, and + key as an entity already in the datastore, the entity from the file + overwrites the entity in the datastore. + + Also sets __next_id to one greater than the highest id allocated so far. + """ + if self.__datastore_file and self.__datastore_file != '/dev/null': + for encoded_entity in self.__ReadPickled(self.__datastore_file): + try: + entity = entity_pb.EntityProto(encoded_entity) + except self.READ_PB_EXCEPTIONS, e: + raise datastore_errors.InternalError(self.READ_ERROR_MSG % + (self.__datastore_file, e)) + except struct.error, e: + if (sys.version_info[0:3] == (2, 5, 0) + and e.message.startswith('unpack requires a string argument')): + raise datastore_errors.InternalError(self.READ_PY250_MSG + + self.READ_ERROR_MSG % + (self.__datastore_file, e)) + else: + raise + + self._StoreEntity(entity) + + last_path = entity.key().path().element_list()[-1] + if last_path.has_id() and last_path.id() >= self.__next_id: + self.__next_id = last_path.id() + 1 + + def Write(self): + """ Writes out the datastore and history files. Be careful! If the files + already exist, this method overwrites them! + """ + self.__WriteDatastore() + + def __WriteDatastore(self): + """ Writes out the datastore file. Be careful! If the file already exist, + this method overwrites it! + """ + if self.__datastore_file and self.__datastore_file != '/dev/null': + encoded = [] + for kind_dict in self.__entities.values(): + for entity in kind_dict.values(): + encoded.append(entity.encoded_protobuf) + + self.__WritePickled(encoded, self.__datastore_file) + + def __ReadPickled(self, filename): + """Reads a pickled object from the given file and returns it. + """ + self.__file_lock.acquire() + + try: + try: + if filename and filename != '/dev/null' and os.path.isfile(filename): + return pickle.load(open(filename, 'rb')) + else: + logging.warning('Could not read datastore data from %s', filename) + except (AttributeError, LookupError, ImportError, NameError, TypeError, + ValueError, struct.error, pickle.PickleError), e: + raise datastore_errors.InternalError( + 'Could not read data from %s. Try running with the ' + '--clear_datastore flag. Cause:\n%r' % (filename, e)) + finally: + self.__file_lock.release() + + return [] + + def __WritePickled(self, obj, filename, openfile=file): + """Pickles the object and writes it to the given file. + """ + if not filename or filename == '/dev/null' or not obj: + return + + tmpfile = openfile(os.tempnam(os.path.dirname(filename)), 'wb') + + pickler = pickle.Pickler(tmpfile, protocol=1) + pickler.fast = True + pickler.dump(obj) + + tmpfile.close() + + self.__file_lock.acquire() + try: + try: + os.rename(tmpfile.name, filename) + except OSError: + try: + os.remove(filename) + except: + pass + os.rename(tmpfile.name, filename) + finally: + self.__file_lock.release() + + def MakeSyncCall(self, service, call, request, response): + """ The main RPC entry point. service must be 'datastore_v3'. + """ + self.assertPbIsInitialized(request) + super(DatastoreFileStub, self).MakeSyncCall(service, + call, + request, + response) + self.assertPbIsInitialized(response) + + def assertPbIsInitialized(self, pb): + """Raises an exception if the given PB is not initialized and valid.""" + explanation = [] + assert pb.IsInitialized(explanation), explanation + pb.Encode() + + def QueryHistory(self): + """Returns a dict that maps Query PBs to times they've been run. + """ + return dict((pb, times) for pb, times in self.__query_history.items() + if pb.app() == self.__app_id) + + def _Dynamic_Put(self, put_request, put_response): + if put_request.has_transaction(): + self.__ValidateTransaction(put_request.transaction()) + + clones = [] + for entity in put_request.entity_list(): + self.__ValidateKey(entity.key()) + + clone = entity_pb.EntityProto() + clone.CopyFrom(entity) + + for property in clone.property_list() + clone.raw_property_list(): + if property.value().has_uservalue(): + uid = md5.new(property.value().uservalue().email().lower()).digest() + uid = '1' + ''.join(['%02d' % ord(x) for x in uid])[:20] + property.mutable_value().mutable_uservalue().set_obfuscated_gaiaid( + uid) + + clones.append(clone) + + assert clone.has_key() + assert clone.key().path().element_size() > 0 + + last_path = clone.key().path().element_list()[-1] + if last_path.id() == 0 and not last_path.has_name(): + self.__id_lock.acquire() + last_path.set_id(self.__next_id) + self.__next_id += 1 + self.__id_lock.release() + + assert clone.entity_group().element_size() == 0 + group = clone.mutable_entity_group() + root = clone.key().path().element(0) + group.add_element().CopyFrom(root) + + else: + assert (clone.has_entity_group() and + clone.entity_group().element_size() > 0) + + self.__entities_lock.acquire() + + try: + for clone in clones: + self._StoreEntity(clone) + finally: + self.__entities_lock.release() + + if not put_request.has_transaction(): + self.__WriteDatastore() + + put_response.key_list().extend([c.key() for c in clones]) + + def _Dynamic_Touch(self, get_request, get_response): + pass + + def _Dynamic_Get(self, get_request, get_response): + if get_request.has_transaction(): + self.__ValidateTransaction(get_request.transaction()) + entities = self.__tx_snapshot + else: + entities = self.__entities + + for key in get_request.key_list(): + self.__ValidateAppId(key.app()) + app_kind = self._AppIdNamespaceKindForKey(key) + + group = get_response.add_entity() + try: + entity = entities[app_kind][key].protobuf + except KeyError: + entity = None + + if entity: + group.mutable_entity().CopyFrom(entity) + + + def _Dynamic_Delete(self, delete_request, delete_response): + if delete_request.has_transaction(): + self.__ValidateTransaction(delete_request.transaction()) + + self.__entities_lock.acquire() + try: + for key in delete_request.key_list(): + self.__ValidateAppId(key.app()) + app_kind = self._AppIdNamespaceKindForKey(key) + try: + del self.__entities[app_kind][key] + if not self.__entities[app_kind]: + del self.__entities[app_kind] + + del self.__schema_cache[app_kind] + except KeyError: + pass + + if not delete_request.has_transaction(): + self.__WriteDatastore() + finally: + self.__entities_lock.release() + + + def _Dynamic_RunQuery(self, query, query_result): + if query.has_transaction(): + self.__ValidateTransaction(query.transaction()) + if not query.has_ancestor(): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Only ancestor queries are allowed inside transactions.') + entities = self.__tx_snapshot + else: + entities = self.__entities + + app_id = query.app() + namespace = query.name_space() + self.__ValidateAppId(app_id) + + num_components = len(query.filter_list()) + len(query.order_list()) + if query.has_ancestor(): + num_components += 1 + if num_components > _MAX_QUERY_COMPONENTS: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + ('query is too large. may not have more than %s filters' + ' + sort orders ancestor total' % _MAX_QUERY_COMPONENTS)) + + (filters, orders) = datastore_index.Normalize(query.filter_list(), + query.order_list()) + + if self.__require_indexes: + required, kind, ancestor, props, num_eq_filters = datastore_index.CompositeIndexForQuery(query) + if required: + required_key = kind, ancestor, props + indexes = self.__indexes.get(app_id) + if not indexes: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.NEED_INDEX, + "This query requires a composite index, but none are defined. " + "You must create an index.yaml file in your application root.") + eq_filters_set = set(props[:num_eq_filters]) + remaining_filters = props[num_eq_filters:] + for index in indexes: + definition = datastore_index.ProtoToIndexDefinition(index) + index_key = datastore_index.IndexToKey(definition) + if required_key == index_key: + break + if num_eq_filters > 1 and (kind, ancestor) == index_key[:2]: + this_props = index_key[2] + this_eq_filters_set = set(this_props[:num_eq_filters]) + this_remaining_filters = this_props[num_eq_filters:] + if (eq_filters_set == this_eq_filters_set and + remaining_filters == this_remaining_filters): + break + else: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.NEED_INDEX, + "This query requires a composite index that is not defined. " + "You must update the index.yaml file in your application root.") + + try: + query.set_app(app_id) + datastore_types.SetNamespace(query, namespace) + encoded = datastore_types.EncodeAppIdNamespace(app_id, namespace) + if query.has_kind(): + results = entities[encoded, query.kind()].values() + results = [entity.native for entity in results] + else: + results = [] + for key in entities: + if key[0] == encoded: + results += [entity.native for entity in entities[key].values()] + except KeyError: + results = [] + + if query.has_ancestor(): + ancestor_path = query.ancestor().path().element_list() + def is_descendant(entity): + path = entity.key()._Key__reference.path().element_list() + return path[:len(ancestor_path)] == ancestor_path + results = filter(is_descendant, results) + + operators = {datastore_pb.Query_Filter.LESS_THAN: '<', + datastore_pb.Query_Filter.LESS_THAN_OR_EQUAL: '<=', + datastore_pb.Query_Filter.GREATER_THAN: '>', + datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL: '>=', + datastore_pb.Query_Filter.EQUAL: '==', + } + + def has_prop_indexed(entity, prop): + """Returns True if prop is in the entity and is indexed.""" + if prop in datastore_types._SPECIAL_PROPERTIES: + return True + elif prop in entity.unindexed_properties(): + return False + + values = entity.get(prop, []) + if not isinstance(values, (tuple, list)): + values = [values] + + for value in values: + if type(value) not in datastore_types._RAW_PROPERTY_TYPES: + return True + return False + + for filt in filters: + assert filt.op() != datastore_pb.Query_Filter.IN + + prop = filt.property(0).name().decode('utf-8') + op = operators[filt.op()] + + filter_val_list = [datastore_types.FromPropertyPb(filter_prop) + for filter_prop in filt.property_list()] + + def passes_filter(entity): + """Returns True if the entity passes the filter, False otherwise. + + The filter being evaluated is filt, the current filter that we're on + in the list of filters in the query. + """ + if not has_prop_indexed(entity, prop): + return False + + try: + entity_vals = datastore._GetPropertyValue(entity, prop) + except KeyError: + entity_vals = [] + + if not isinstance(entity_vals, list): + entity_vals = [entity_vals] + + for fixed_entity_val in entity_vals: + for filter_val in filter_val_list: + fixed_entity_type = self._PROPERTY_TYPE_TAGS.get( + fixed_entity_val.__class__) + filter_type = self._PROPERTY_TYPE_TAGS.get(filter_val.__class__) + if fixed_entity_type == filter_type: + comp = u'%r %s %r' % (fixed_entity_val, op, filter_val) + elif op != '==': + comp = '%r %s %r' % (fixed_entity_type, op, filter_type) + else: + continue + + logging.log(logging.DEBUG - 1, + 'Evaling filter expression "%s"', comp) + + try: + ret = eval(comp) + if ret and ret != NotImplementedError: + return True + except TypeError: + pass + + return False + + results = filter(passes_filter, results) + + for order in orders: + prop = order.property().decode('utf-8') + results = [entity for entity in results if has_prop_indexed(entity, prop)] + + def order_compare_entities(a, b): + """ Return a negative, zero or positive number depending on whether + entity a is considered smaller than, equal to, or larger than b, + according to the query's orderings. """ + cmped = 0 + for o in orders: + prop = o.property().decode('utf-8') + + reverse = (o.direction() is datastore_pb.Query_Order.DESCENDING) + + a_val = datastore._GetPropertyValue(a, prop) + if isinstance(a_val, list): + a_val = sorted(a_val, order_compare_properties, reverse=reverse)[0] + + b_val = datastore._GetPropertyValue(b, prop) + if isinstance(b_val, list): + b_val = sorted(b_val, order_compare_properties, reverse=reverse)[0] + + cmped = order_compare_properties(a_val, b_val) + + if o.direction() is datastore_pb.Query_Order.DESCENDING: + cmped = -cmped + + if cmped != 0: + return cmped + + if cmped == 0: + return cmp(a.key(), b.key()) + + def order_compare_properties(x, y): + """Return a negative, zero or positive number depending on whether + property value x is considered smaller than, equal to, or larger than + property value y. If x and y are different types, they're compared based + on the type ordering used in the real datastore, which is based on the + tag numbers in the PropertyValue PB. + """ + if isinstance(x, datetime.datetime): + x = datastore_types.DatetimeToTimestamp(x) + if isinstance(y, datetime.datetime): + y = datastore_types.DatetimeToTimestamp(y) + + x_type = self._PROPERTY_TYPE_TAGS.get(x.__class__) + y_type = self._PROPERTY_TYPE_TAGS.get(y.__class__) + + if x_type == y_type: + try: + return cmp(x, y) + except TypeError: + return 0 + else: + return cmp(x_type, y_type) + + results.sort(order_compare_entities) + + clone = datastore_pb.Query() + clone.CopyFrom(query) + clone.clear_hint() + clone.clear_limit() + clone.clear_offset() + if clone in self.__query_history: + self.__query_history[clone] += 1 + else: + self.__query_history[clone] = 1 + + cursor = _Cursor(query, results, order_compare_entities) + self.__queries[cursor.cursor] = cursor + + if query.has_count(): + count = query.count() + elif query.has_limit(): + count = query.limit() + else: + count = _BATCH_SIZE + + cursor.PopulateQueryResult(query_result, count, + query.offset(), compile=query.compile()) + + if query.compile(): + compiled_query = query_result.mutable_compiled_query() + compiled_query.set_keys_only(query.keys_only()) + compiled_query.mutable_primaryscan().set_index_name(query.Encode()) + + def _Dynamic_Next(self, next_request, query_result): + self.__ValidateAppId(next_request.cursor().app()) + + cursor_handle = next_request.cursor().cursor() + + try: + cursor = self.__queries[cursor_handle] + except KeyError: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, 'Cursor %d not found' % cursor_handle) + + assert cursor.app == next_request.cursor().app() + + count = _BATCH_SIZE + if next_request.has_count(): + count = next_request.count() + cursor.PopulateQueryResult(query_result, + count, next_request.offset(), + next_request.compile()) + + def _Dynamic_Count(self, query, integer64proto): + query_result = datastore_pb.QueryResult() + self._Dynamic_RunQuery(query, query_result) + cursor = query_result.cursor().cursor() + integer64proto.set_value(min(self.__queries[cursor].count, _MAXIMUM_RESULTS)) + del self.__queries[cursor] + + def _Dynamic_BeginTransaction(self, request, transaction): + self.__ValidateAppId(request.app()) + + self.__tx_handle_lock.acquire() + handle = self.__next_tx_handle + self.__next_tx_handle += 1 + self.__tx_handle_lock.release() + + transaction.set_app(request.app()) + transaction.set_handle(handle) + assert transaction not in self.__transactions + self.__transactions.add(transaction) + + self.__tx_lock.acquire() + snapshot = [(app_kind, dict(entities)) + for app_kind, entities in self.__entities.items()] + self.__tx_snapshot = dict(snapshot) + self.__tx_actions = [] + + def _Dynamic_AddActions(self, request, _): + """Associates the creation of one or more tasks with a transaction. + + Args: + request: A taskqueue_service_pb.TaskQueueBulkAddRequest containing the + tasks that should be created when the transaction is comitted. + """ + + + if ((len(self.__tx_actions) + request.add_request_size()) > + _MAX_ACTIONS_PER_TXN): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Too many messages, maximum allowed %s' % _MAX_ACTIONS_PER_TXN) + + new_actions = [] + for add_request in request.add_request_list(): + self.__ValidateTransaction(add_request.transaction()) + clone = taskqueue_service_pb.TaskQueueAddRequest() + clone.CopyFrom(add_request) + clone.clear_transaction() + new_actions.append(clone) + + self.__tx_actions.extend(new_actions) + + def _Dynamic_Commit(self, transaction, transaction_response): + self.__ValidateTransaction(transaction) + + self.__tx_snapshot = {} + try: + self.__WriteDatastore() + + for action in self.__tx_actions: + try: + apiproxy_stub_map.MakeSyncCall( + 'taskqueue', 'Add', action, api_base_pb.VoidProto()) + except apiproxy_errors.ApplicationError, e: + logging.warning('Transactional task %s has been dropped, %s', + action, e) + pass + + finally: + self.__tx_actions = [] + self.__tx_lock.release() + + def _Dynamic_Rollback(self, transaction, transaction_response): + self.__ValidateTransaction(transaction) + + self.__entities = self.__tx_snapshot + self.__tx_snapshot = {} + self.__tx_actions = [] + self.__tx_lock.release() + + def _Dynamic_GetSchema(self, req, schema): + app_str = req.app() + self.__ValidateAppId(app_str) + + namespace_str = req.name_space() + app_namespace_str = datastore_types.EncodeAppIdNamespace(app_str, + namespace_str) + kinds = [] + + for app_namespace, kind in self.__entities: + if (app_namespace != app_namespace_str or + (req.has_start_kind() and kind < req.start_kind()) or + (req.has_end_kind() and kind > req.end_kind())): + continue + + app_kind = (app_namespace_str, kind) + if app_kind in self.__schema_cache: + kinds.append(self.__schema_cache[app_kind]) + continue + + kind_pb = entity_pb.EntityProto() + kind_pb.mutable_key().set_app('') + kind_pb.mutable_key().mutable_path().add_element().set_type(kind) + kind_pb.mutable_entity_group() + + props = {} + + for entity in self.__entities[app_kind].values(): + for prop in entity.protobuf.property_list(): + if prop.name() not in props: + props[prop.name()] = entity_pb.PropertyValue() + props[prop.name()].MergeFrom(prop.value()) + + for value_pb in props.values(): + if value_pb.has_int64value(): + value_pb.set_int64value(0) + if value_pb.has_booleanvalue(): + value_pb.set_booleanvalue(False) + if value_pb.has_stringvalue(): + value_pb.set_stringvalue('none') + if value_pb.has_doublevalue(): + value_pb.set_doublevalue(0.0) + if value_pb.has_pointvalue(): + value_pb.mutable_pointvalue().set_x(0.0) + value_pb.mutable_pointvalue().set_y(0.0) + if value_pb.has_uservalue(): + value_pb.mutable_uservalue().set_gaiaid(0) + value_pb.mutable_uservalue().set_email('none') + value_pb.mutable_uservalue().set_auth_domain('none') + value_pb.mutable_uservalue().clear_nickname() + value_pb.mutable_uservalue().clear_obfuscated_gaiaid() + if value_pb.has_referencevalue(): + value_pb.clear_referencevalue() + value_pb.mutable_referencevalue().set_app('none') + pathelem = value_pb.mutable_referencevalue().add_pathelement() + pathelem.set_type('none') + pathelem.set_name('none') + + for name, value_pb in props.items(): + prop_pb = kind_pb.add_property() + prop_pb.set_name(name) + prop_pb.set_multiple(False) + prop_pb.mutable_value().CopyFrom(value_pb) + + kinds.append(kind_pb) + self.__schema_cache[app_kind] = kind_pb + + for kind_pb in kinds: + kind = schema.add_kind() + kind.CopyFrom(kind_pb) + if not req.properties(): + kind.clear_property() + + schema.set_more_results(False) + + def _Dynamic_AllocateIds(self, allocate_ids_request, allocate_ids_response): + model_key = allocate_ids_request.model_key() + + self.__ValidateAppId(model_key.app()) + + if allocate_ids_request.has_size() and allocate_ids_request.has_max(): + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Both size and max cannot be set.') + try: + self.__id_lock.acquire() + start = self.__next_id + if allocate_ids_request.has_size(): + self.__next_id += allocate_ids_request.size() + elif allocate_ids_request.has_max(): + self.__next_id = max(self.__next_id, allocate_ids_request.max() + 1) + end = self.__next_id - 1 + finally: + self.__id_lock.release() + + allocate_ids_response.set_start(start) + allocate_ids_response.set_end(end) + + def _Dynamic_CreateIndex(self, index, id_response): + self.__ValidateAppId(index.app_id()) + if index.id() != 0: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'New index id must be 0.') + elif self.__FindIndex(index): + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Index already exists.') + + self.__index_id_lock.acquire() + index.set_id(self.__next_index_id) + id_response.set_value(self.__next_index_id) + self.__next_index_id += 1 + self.__index_id_lock.release() + + clone = entity_pb.CompositeIndex() + clone.CopyFrom(index) + app = index.app_id() + clone.set_app_id(app) + + self.__indexes_lock.acquire() + try: + if app not in self.__indexes: + self.__indexes[app] = [] + self.__indexes[app].append(clone) + finally: + self.__indexes_lock.release() + + def _Dynamic_GetIndices(self, app_str, composite_indices): + self.__ValidateAppId(app_str.value()) + composite_indices.index_list().extend( + self.__indexes.get(app_str.value(), [])) + + def _Dynamic_UpdateIndex(self, index, void): + self.__ValidateAppId(index.app_id()) + stored_index = self.__FindIndex(index) + if not stored_index: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + "Index doesn't exist.") + elif (index.state() != stored_index.state() and + index.state() not in self._INDEX_STATE_TRANSITIONS[ + stored_index.state()]): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + "cannot move index state from %s to %s" % + (entity_pb.CompositeIndex.State_Name(stored_index.state()), + (entity_pb.CompositeIndex.State_Name(index.state())))) + + self.__indexes_lock.acquire() + try: + stored_index.set_state(index.state()) + finally: + self.__indexes_lock.release() + + def _Dynamic_DeleteIndex(self, index, void): + self.__ValidateAppId(index.app_id()) + stored_index = self.__FindIndex(index) + if not stored_index: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + "Index doesn't exist.") + + app = index.app_id() + self.__indexes_lock.acquire() + try: + self.__indexes[app].remove(stored_index) + finally: + self.__indexes_lock.release() + + def __FindIndex(self, index): + """Finds an existing index by definition. + + Args: + definition: entity_pb.CompositeIndex + + Returns: + entity_pb.CompositeIndex, if it exists; otherwise None + """ + app = index.app_id() + self.__ValidateAppId(app) + if app in self.__indexes: + for stored_index in self.__indexes[app]: + if index.definition() == stored_index.definition(): + return stored_index + + return None diff --git a/google_appengine/google/appengine/api/datastore_file_stub.pyc b/google_appengine/google/appengine/api/datastore_file_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c54f6bf38244bbc461c9e9342300799c5478ee45 GIT binary patch literal 46256 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 D=jcEm literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/datastore_types.py b/google_appengine/google/appengine/api/datastore_types.py new file mode 100755 index 0000000..0aa9037 --- /dev/null +++ b/google_appengine/google/appengine/api/datastore_types.py @@ -0,0 +1,1722 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Higher-level, semantic data types for the datastore. These types +are expected to be set as attributes of Entities. See "Supported Data Types" +in the API Guide. + +Most of these types are based on XML elements from Atom and GData elements +from the atom and gd namespaces. For more information, see: + + http://www.atomenabled.org/developers/syndication/ + http://code.google.com/apis/gdata/common-elements.html + +The namespace schemas are: + + http://www.w3.org/2005/Atom + http://schemas.google.com/g/2005 +""" + + + + + +import base64 +import calendar +import datetime +import os +import re +import string +import time +import urlparse +from xml.sax import saxutils +from google.appengine.datastore import datastore_pb +from google.appengine.api import datastore_errors +from google.appengine.api import users +from google.appengine.api import namespace_manager +from google.net.proto import ProtocolBuffer +from google.appengine.datastore import entity_pb + +_MAX_STRING_LENGTH = 500 + +_MAX_LINK_PROPERTY_LENGTH = 2083 + +RESERVED_PROPERTY_NAME = re.compile('^__.*__$') + +_KEY_SPECIAL_PROPERTY = '__key__' +_UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY = '__unapplied_log_timestamp_us__' +_SPECIAL_PROPERTIES = frozenset( + [_KEY_SPECIAL_PROPERTY, _UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY]) + +_NAMESPACE_SEPARATOR='!' + +class UtcTzinfo(datetime.tzinfo): + def utcoffset(self, dt): return datetime.timedelta(0) + def dst(self, dt): return datetime.timedelta(0) + def tzname(self, dt): return 'UTC' + def __repr__(self): return 'datastore_types.UTC' + +UTC = UtcTzinfo() + + +def typename(obj): + """Returns the type of obj as a string. More descriptive and specific than + type(obj), and safe for any object, unlike __class__.""" + if hasattr(obj, '__class__'): + return getattr(obj, '__class__').__name__ + else: + return type(obj).__name__ + + +def ValidateString(value, + name='unused', + exception=datastore_errors.BadValueError, + max_len=_MAX_STRING_LENGTH, + empty_ok=False): + """Raises an exception if value is not a valid string or a subclass thereof. + + A string is valid if it's not empty, no more than _MAX_STRING_LENGTH bytes, + and not a Blob. The exception type can be specified with the exception + argument; it defaults to BadValueError. + + Args: + value: the value to validate. + name: the name of this value; used in the exception message. + exception: the type of exception to raise. + max_len: the maximum allowed length, in bytes. + empty_ok: allow empty value. + """ + if value is None and empty_ok: + return + if not isinstance(value, basestring) or isinstance(value, Blob): + raise exception('%s should be a string; received %s (a %s):' % + (name, value, typename(value))) + if not value and not empty_ok: + raise exception('%s must not be empty.' % name) + + if len(value.encode('utf-8')) > max_len: + raise exception('%s must be under %d bytes.' % (name, max_len)) + + +def ValidateInteger(value, + name='unused', + exception=datastore_errors.BadValueError, + empty_ok=False, + zero_ok=False, + negative_ok=False): + """Raises an exception if value is not a valid integer. + + An integer is valid if it's not negative or empty and is an integer + (either int or long). The exception type raised can be specified + with the exception argument; it defaults to BadValueError. + + Args: + value: the value to validate. + name: the name of this value; used in the exception message. + exception: the type of exception to raise. + empty_ok: allow None value. + zero_ok: allow zero value. + negative_ok: allow negative value. + """ + if value is None and empty_ok: + return + if not isinstance(value, (int, long)): + raise exception('%s should be an integer; received %s (a %s).' % + (name, value, typename(value))) + if not value and not zero_ok: + raise exception('%s must not be 0 (zero)' % name) + if value < 0 and not negative_ok: + raise exception('%s must not be negative.' % name) + +def ResolveAppId(app): + """Validate app id, providing a default. + + If the argument is None, $APPLICATION_ID is substituted. + + Args: + app: The app id argument value to be validated. + + Returns: + The value of app, or the substituted default. Always a non-empty string. + + Raises: + BadArgumentError if the value is empty or not a string. + """ + if app is None: + app = os.environ.get('APPLICATION_ID', '') + ValidateString(app, 'app', datastore_errors.BadArgumentError) + return app + + +def ResolveNamespace(namespace): + """Validate app namespace, providing a default. + + If the argument is None, namespace_manager.get_namespace() is substituted. + + Args: + namespace: The namespace argument value to be validated. + + Returns: + The value of namespace, or the substituted default. + Always a non-empty string or None. + + Raises: + BadArgumentError if the value is not a string. + """ + if namespace is None: + namespace = namespace_manager.get_namespace() + else: + namespace_manager.validate_namespace( + namespace, datastore_errors.BadArgumentError) + return namespace + + +def EncodeAppIdNamespace(app_id, namespace): + """Concatenates app id and namespace into a single string. + + This method is needed for xml and datastore_file_stub. + """ + if not namespace: + return app_id + else: + return app_id + _NAMESPACE_SEPARATOR + namespace + + +def SetNamespace(proto, namespace): + """Sets the namespace for a protocol buffer or clears the field. + + Args: + proto: the protocol buffer to update + namespace: the new namespace (None or an empty string will clear out the + field). + """ + if not namespace: + proto.clear_name_space() + else: + proto.set_name_space(namespace) + + +def PartitionString(value, separator): + """Equivalent to python2.5 str.partition() + TODO(gmariani) use str.partition() when python 2.5 is adopted. + + Args: + value: String to be partitioned + separator: Separator string + """ + index = value.find(separator) + if index == -1: + return (value, '', value[0:0]) + else: + return (value[0:index], separator, value[index+len(separator):len(value)]) + + + +class Key(object): + """The primary key for a datastore entity. + + A datastore GUID. A Key instance uniquely identifies an entity across all + apps, and includes all information necessary to fetch the entity from the + datastore with Get(). + + Key implements __hash__, and key instances are immutable, so Keys may be + used in sets and as dictionary keys. + """ + __reference = None + + def __init__(self, encoded=None): + """Constructor. Creates a Key from a string. + + Args: + # a base64-encoded primary key, generated by Key.__str__ + encoded: str + """ + self._str = None + if encoded is not None: + if not isinstance(encoded, basestring): + try: + repr_encoded = repr(encoded) + except: + repr_encoded = "" + raise datastore_errors.BadArgumentError( + 'Key() expects a string; received %s (a %s).' % + (repr_encoded, typename(encoded))) + try: + modulo = len(encoded) % 4 + if modulo != 0: + encoded += ('=' * (4 - modulo)) + + self._str = str(encoded) + encoded_pb = base64.urlsafe_b64decode(self._str) + self.__reference = entity_pb.Reference(encoded_pb) + assert self.__reference.IsInitialized() + + self._str = self._str.rstrip('=') + + except (AssertionError, TypeError), e: + raise datastore_errors.BadKeyError( + 'Invalid string key %s. Details: %s' % (encoded, e)) + except Exception, e: + if e.__class__.__name__ == 'ProtocolBufferDecodeError': + raise datastore_errors.BadKeyError('Invalid string key %s.' % encoded) + else: + raise + else: + self.__reference = entity_pb.Reference() + + def to_path(self, _default_id=None): + """Construct the "path" of this key as a list. + + Returns: + A list [kind_1, id_or_name_1, ..., kind_n, id_or_name_n] of the key path. + + Raises: + datastore_errors.BadKeyError if this key does not have a valid path. + """ + + path = [] + for path_element in self.__reference.path().element_list(): + path.append(path_element.type().decode('utf-8')) + if path_element.has_name(): + path.append(path_element.name().decode('utf-8')) + elif path_element.has_id(): + path.append(path_element.id()) + elif _default_id is not None: + path.append(_default_id) + else: + raise datastore_errors.BadKeyError('Incomplete key found in to_path') + return path + + @staticmethod + def from_path(*args, **kwds): + """Static method to construct a Key out of a "path" (kind, id or name, ...). + + This is useful when an application wants to use just the id or name portion + of a key in e.g. a URL, where the rest of the URL provides enough context to + fill in the rest, i.e. the app id (always implicit), the entity kind, and + possibly an ancestor key. Since ids and names are usually small, they're + more attractive for use in end-user-visible URLs than the full string + representation of a key. + + Args: + kind: the entity kind (a str or unicode instance) + id_or_name: the id (an int or long) or name (a str or unicode instance) + parent: optional parent Key; default None. + namespace: optional namespace to use otherwise namespace_manager's + default namespace is used. + + Returns: + A new Key instance whose .kind() and .id() or .name() methods return + the *last* kind and id or name positional arguments passed. + + Raises: + BadArgumentError for invalid arguments. + BadKeyError if the parent key is incomplete. + """ + parent = kwds.pop('parent', None) + app_id = ResolveAppId(kwds.pop('_app', None)) + + namespace = kwds.pop('namespace', None) + + if kwds: + raise datastore_errors.BadArgumentError( + 'Excess keyword arguments ' + repr(kwds)) + + if not args or len(args) % 2: + raise datastore_errors.BadArgumentError( + 'A non-zero even number of positional arguments is required ' + '(kind, id or name, kind, id or name, ...); received %s' % repr(args)) + + if parent is not None: + if not isinstance(parent, Key): + raise datastore_errors.BadArgumentError( + 'Expected None or a Key as parent; received %r (a %s).' % + (parent, typename(parent))) + if namespace is None: + namespace = parent.namespace() + if not parent.has_id_or_name(): + raise datastore_errors.BadKeyError( + 'The parent Key is incomplete.') + if app_id != parent.app() or namespace != parent.namespace(): + raise datastore_errors.BadArgumentError( + 'The app/namespace arguments (%s/%s) should match ' + 'parent.app/namespace() (%s/%s)' % + (app_id, namespace, parent.app(), parent.namespace())) + + namespace = ResolveNamespace(namespace) + + key = Key() + ref = key.__reference + if parent is not None: + ref.CopyFrom(parent.__reference) + else: + ref.set_app(app_id) + SetNamespace(ref, namespace) + + path = ref.mutable_path() + for i in xrange(0, len(args), 2): + kind, id_or_name = args[i:i+2] + if isinstance(kind, basestring): + kind = kind.encode('utf-8') + else: + raise datastore_errors.BadArgumentError( + 'Expected a string kind as argument %d; received %r (a %s).' % + (i + 1, kind, typename(kind))) + elem = path.add_element() + elem.set_type(kind) + if isinstance(id_or_name, (int, long)): + elem.set_id(id_or_name) + elif isinstance(id_or_name, basestring): + ValidateString(id_or_name, 'name') + elem.set_name(id_or_name.encode('utf-8')) + else: + raise datastore_errors.BadArgumentError( + 'Expected an integer id or string name as argument %d; ' + 'received %r (a %s).' % (i + 2, id_or_name, typename(id_or_name))) + + assert ref.IsInitialized() + return key + + def app(self): + """Returns this entity's app id, a string.""" + if self.__reference.app(): + return self.__reference.app().decode('utf-8') + else: + return None + + def namespace(self): + """Returns this entity's namespace, a string.""" + if self.__reference.has_name_space(): + return self.__reference.name_space().decode('utf-8') + else: + return '' + + def kind(self): + """Returns this entity's kind, as a string.""" + if self.__reference.path().element_size() > 0: + encoded = self.__reference.path().element_list()[-1].type() + return unicode(encoded.decode('utf-8')) + else: + return None + + def id(self): + """Returns this entity's id, or None if it doesn't have one.""" + elems = self.__reference.path().element_list() + if elems and elems[-1].has_id() and elems[-1].id(): + return elems[-1].id() + else: + return None + + def name(self): + """Returns this entity's name, or None if it doesn't have one.""" + elems = self.__reference.path().element_list() + if elems and elems[-1].has_name() and elems[-1].name(): + return elems[-1].name().decode('utf-8') + else: + return None + + def id_or_name(self): + """Returns this entity's id or name, whichever it has, or None.""" + if self.id() is not None: + return self.id() + else: + return self.name() + + def has_id_or_name(self): + """Returns True if this entity has an id or name, False otherwise. + """ + return self.id_or_name() is not None + + def parent(self): + """Returns this entity's parent, as a Key. If this entity has no parent, + returns None.""" + if self.__reference.path().element_size() > 1: + parent = Key() + parent.__reference.CopyFrom(self.__reference) + parent.__reference.path().element_list().pop() + return parent + else: + return None + + def ToTagUri(self): + """Returns a tag: URI for this entity for use in XML output. + + Foreign keys for entities may be represented in XML output as tag URIs. + RFC 4151 describes the tag URI scheme. From http://taguri.org/: + + The tag algorithm lets people mint - create - identifiers that no one + else using the same algorithm could ever mint. It is simple enough to do + in your head, and the resulting identifiers can be easy to read, write, + and remember. The identifiers conform to the URI (URL) Syntax. + + Tag URIs for entities use the app's auth domain and the date that the URI + is generated. The namespace-specific part is []. + + For example, here is the tag URI for a Kitten with the key "Fluffy" in the + catsinsinks app: + + tag:catsinsinks.googleapps.com,2006-08-29:Kitten[Fluffy] + + Raises a BadKeyError if this entity's key is incomplete. + """ + if not self.has_id_or_name(): + raise datastore_errors.BadKeyError( + 'ToTagUri() called for an entity with an incomplete key.') + + return u'tag:%s.%s,%s:%s[%s]' % ( + + saxutils.escape(EncodeAppIdNamespace(self.app(), self.namespace())), + os.environ['AUTH_DOMAIN'], + datetime.date.today().isoformat(), + saxutils.escape(self.kind()), + saxutils.escape(str(self))) + + ToXml = ToTagUri + + def entity_group(self): + """Returns this key's entity group as a Key. + + Note that the returned Key will be incomplete if this Key is for a root + entity and it is incomplete. + """ + group = Key._FromPb(self.__reference) + del group.__reference.path().element_list()[1:] + return group + + @staticmethod + def _FromPb(pb): + """Static factory method. Creates a Key from an entity_pb.Reference. + + Not intended to be used by application developers. Enforced by hiding the + entity_pb classes. + + Args: + pb: entity_pb.Reference + """ + if not isinstance(pb, entity_pb.Reference): + raise datastore_errors.BadArgumentError( + 'Key constructor takes an entity_pb.Reference; received %s (a %s).' % + (pb, typename(pb))) + + key = Key() + key.__reference = entity_pb.Reference() + key.__reference.CopyFrom(pb) + return key + + def _ToPb(self): + """Converts this Key to its protocol buffer representation. + + Not intended to be used by application developers. Enforced by hiding the + entity_pb classes. + + Returns: + # the Reference PB representation of this Key + entity_pb.Reference + """ + pb = entity_pb.Reference() + pb.CopyFrom(self.__reference) + if not self.has_id_or_name(): + pb.mutable_path().element_list()[-1].set_id(0) + + pb.app().decode('utf-8') + for pathelem in pb.path().element_list(): + pathelem.type().decode('utf-8') + + return pb + + def __str__(self): + """Encodes this Key as an opaque string. + + Returns a string representation of this key, suitable for use in HTML, + URLs, and other similar use cases. If the entity's key is incomplete, + raises a BadKeyError. + + Unfortunately, this string encoding isn't particularly compact, and its + length varies with the length of the path. If you want a shorter identifier + and you know the kind and parent (if any) ahead of time, consider using just + the entity's id or name. + + Returns: + string + """ + try: + if self._str is not None: + return self._str + except AttributeError: + pass + if (self.has_id_or_name()): + encoded = base64.urlsafe_b64encode(self.__reference.Encode()) + self._str = encoded.replace('=', '') + else: + raise datastore_errors.BadKeyError( + 'Cannot string encode an incomplete key!\n%s' % self.__reference) + return self._str + + + def __repr__(self): + """Returns an eval()able string representation of this key. + + Returns a Python string of the form 'datastore_types.Key.from_path(...)' + that can be used to recreate this key. + + Returns: + string + """ + args = [] + for elem in self.__reference.path().element_list(): + args.append(repr(elem.type().decode('utf-8'))) + if elem.has_name(): + args.append(repr(elem.name().decode('utf-8'))) + else: + args.append(repr(elem.id())) + + args.append('_app=%r' % self.__reference.app().decode('utf-8')) + if self.__reference.has_name_space(): + args.append('namespace=%r' % + self.__reference.name_space().decode('utf-8')) + return u'datastore_types.Key.from_path(%s)' % ', '.join(args) + + def __cmp__(self, other): + """Returns negative, zero, or positive when comparing two keys. + + TODO(ryanb): for API v2, we should change this to make incomplete keys, ie + keys without an id or name, not equal to any other keys. + + Args: + other: Key to compare to. + + Returns: + Negative if self is less than "other" + Zero if "other" is equal to self + Positive if self is greater than "other" + """ + if not isinstance(other, Key): + return -2 + + self_args = [self.__reference.app(), self.__reference.name_space()] + self_args += self.to_path(_default_id=0) + + other_args = [other.__reference.app(), other.__reference.name_space()] + other_args += other.to_path(_default_id=0) + + for self_component, other_component in zip(self_args, other_args): + comparison = cmp(self_component, other_component) + if comparison != 0: + return comparison + + return cmp(len(self_args), len(other_args)) + + def __hash__(self): + """Returns a 32-bit integer hash of this key. + + Implements Python's hash protocol so that Keys may be used in sets and as + dictionary keys. + + Returns: + int + """ + args = self.to_path(_default_id=0) + args.append(self.__reference.app()) + return hash(type(args)) ^ hash(tuple(args)) + + +class Category(unicode): + """A tag, ie a descriptive word or phrase. Entities may be tagged by users, + and later returned by a queries for that tag. Tags can also be used for + ranking results (frequency), photo captions, clustering, activity, etc. + + Here's a more in-depth description: http://www.zeldman.com/daily/0405d.shtml + + This is the Atom "category" element. In XML output, the tag is provided as + the term attribute. See: + http://www.atomenabled.org/developers/syndication/#category + + Raises BadValueError if tag is not a string or subtype. + """ + TERM = 'user-tag' + + def __init__(self, tag): + super(Category, self).__init__(self, tag) + ValidateString(tag, 'tag') + + def ToXml(self): + return u'' % (Category.TERM, + saxutils.quoteattr(self)) + + +class Link(unicode): + """A fully qualified URL. Usually http: scheme, but may also be file:, ftp:, + news:, among others. + + If you have email (mailto:) or instant messaging (aim:, xmpp:) links, + consider using the Email or IM classes instead. + + This is the Atom "link" element. In XML output, the link is provided as the + href attribute. See: + http://www.atomenabled.org/developers/syndication/#link + + Raises BadValueError if link is not a fully qualified, well-formed URL. + """ + def __init__(self, link): + super(Link, self).__init__(self, link) + ValidateString(link, 'link', max_len=_MAX_LINK_PROPERTY_LENGTH) + + scheme, domain, path, params, query, fragment = urlparse.urlparse(link) + if (not scheme or (scheme != 'file' and not domain) or + (scheme == 'file' and not path)): + raise datastore_errors.BadValueError('Invalid URL: %s' % link) + + def ToXml(self): + return u'' % saxutils.quoteattr(self) + + +class Email(unicode): + """An RFC2822 email address. Makes no attempt at validation; apart from + checking MX records, email address validation is a rathole. + + This is the gd:email element. In XML output, the email address is provided as + the address attribute. See: + http://code.google.com/apis/gdata/common-elements.html#gdEmail + + Raises BadValueError if email is not a valid email address. + """ + def __init__(self, email): + super(Email, self).__init__(self, email) + ValidateString(email, 'email') + + def ToXml(self): + return u'' % saxutils.quoteattr(self) + + +class GeoPt(object): + """A geographical point, specified by floating-point latitude and longitude + coordinates. Often used to integrate with mapping sites like Google Maps. + May also be used as ICBM coordinates. + + This is the georss:point element. In XML output, the coordinates are + provided as the lat and lon attributes. See: http://georss.org/ + + Serializes to ','. Raises BadValueError if it's passed an invalid + serialized string, or if lat and lon are not valid floating points in the + ranges [-90, 90] and [-180, 180], respectively. + """ + lat = None + lon = None + + def __init__(self, lat, lon=None): + if lon is None: + try: + split = lat.split(',') + lat, lon = split + except (AttributeError, ValueError): + raise datastore_errors.BadValueError( + 'Expected a "lat,long" formatted string; received %s (a %s).' % + (lat, typename(lat))) + + try: + lat = float(lat) + lon = float(lon) + if abs(lat) > 90: + raise datastore_errors.BadValueError( + 'Latitude must be between -90 and 90; received %f' % lat) + if abs(lon) > 180: + raise datastore_errors.BadValueError( + 'Longitude must be between -180 and 180; received %f' % lon) + except (TypeError, ValueError): + raise datastore_errors.BadValueError( + 'Expected floats for lat and long; received %s (a %s) and %s (a %s).' % + (lat, typename(lat), lon, typename(lon))) + + self.lat = lat + self.lon = lon + + def __cmp__(self, other): + if not isinstance(other, GeoPt): + try: + other = GeoPt(other) + except datastore_errors.BadValueError: + return NotImplemented + + lat_cmp = cmp(self.lat, other.lat) + if lat_cmp != 0: + return lat_cmp + else: + return cmp(self.lon, other.lon) + + def __hash__(self): + """Returns a 32-bit integer hash of this point. + + Implements Python's hash protocol so that GeoPts may be used in sets and + as dictionary keys. + + Returns: + int + """ + return hash((self.lat, self.lon)) + + def __repr__(self): + """Returns an eval()able string representation of this GeoPt. + + The returned string is of the form 'datastore_types.GeoPt([lat], [lon])'. + + Returns: + string + """ + return 'datastore_types.GeoPt(%r, %r)' % (self.lat, self.lon) + + def __unicode__(self): + return u'%s,%s' % (unicode(self.lat), unicode(self.lon)) + + __str__ = __unicode__ + + def ToXml(self): + return u'%s %s' % (unicode(self.lat), + unicode(self.lon)) + + +class IM(object): + """An instant messaging handle. Includes both an address and its protocol. + The protocol value is either a standard IM scheme or a URL identifying the + IM network for the protocol. Possible values include: + + Value Description + sip SIP/SIMPLE + unknown Unknown or unspecified + xmpp XMPP/Jabber + http://aim.com/ AIM + http://icq.com/ ICQ + http://talk.google.com/ Google Talk + http://messenger.msn.com/ MSN Messenger + http://messenger.yahoo.com/ Yahoo Messenger + http://sametime.com/ Lotus Sametime + http://gadu-gadu.pl/ Gadu-Gadu + + This is the gd:im element. In XML output, the address and protocol are + provided as the address and protocol attributes, respectively. See: + http://code.google.com/apis/gdata/common-elements.html#gdIm + + Serializes to '
'. Raises BadValueError if tag is not a + standard IM scheme or a URL. + """ + PROTOCOLS = [ 'sip', 'unknown', 'xmpp' ] + + protocol = None + address = None + + def __init__(self, protocol, address=None): + if address is None: + try: + split = protocol.split(' ', 1) + protocol, address = split + except (AttributeError, ValueError): + raise datastore_errors.BadValueError( + 'Expected string of format "protocol address"; received %s' % + (protocol,)) + + ValidateString(address, 'address') + if protocol not in self.PROTOCOLS: + Link(protocol) + + self.address = address + self.protocol = protocol + + def __cmp__(self, other): + if not isinstance(other, IM): + try: + other = IM(other) + except datastore_errors.BadValueError: + return NotImplemented + + + return cmp((self.address, self.protocol), + (other.address, other.protocol)) + + def __repr__(self): + """Returns an eval()able string representation of this IM. + + The returned string is of the form: + + datastore_types.IM('address', 'protocol') + + Returns: + string + """ + return 'datastore_types.IM(%r, %r)' % (self.protocol, self.address) + + def __unicode__(self): + return u'%s %s' % (self.protocol, self.address) + + __str__ = __unicode__ + + def ToXml(self): + return (u'' % + (saxutils.quoteattr(self.protocol), + saxutils.quoteattr(self.address))) + + def __len__(self): + return len(unicode(self)) + + +class PhoneNumber(unicode): + """A human-readable phone number or address. + + No validation is performed. Phone numbers have many different formats - + local, long distance, domestic, international, internal extension, TTY, + VOIP, SMS, and alternative networks like Skype, XFire and Roger Wilco. They + all have their own numbering and addressing formats. + + This is the gd:phoneNumber element. In XML output, the phone number is + provided as the text of the element. See: + http://code.google.com/apis/gdata/common-elements.html#gdPhoneNumber + + Raises BadValueError if phone is not a string or subtype. + """ + def __init__(self, phone): + super(PhoneNumber, self).__init__(self, phone) + ValidateString(phone, 'phone') + + def ToXml(self): + return u'%s' % saxutils.escape(self) + + +class PostalAddress(unicode): + """A human-readable mailing address. Again, mailing address formats vary + widely, so no validation is performed. + + This is the gd:postalAddress element. In XML output, the address is provided + as the text of the element. See: + http://code.google.com/apis/gdata/common-elements.html#gdPostalAddress + + Raises BadValueError if address is not a string or subtype. + """ + def __init__(self, address): + super(PostalAddress, self).__init__(self, address) + ValidateString(address, 'address') + + def ToXml(self): + return u'%s' % saxutils.escape(self) + + +class Rating(long): + """A user-provided integer rating for a piece of content. Normalized to a + 0-100 scale. + + This is the gd:rating element. In XML output, the address is provided + as the text of the element. See: + http://code.google.com/apis/gdata/common-elements.html#gdRating + + Serializes to the decimal string representation of the rating. Raises + BadValueError if the rating is not an integer in the range [0, 100]. + """ + MIN = 0 + MAX = 100 + + def __init__(self, rating): + super(Rating, self).__init__(self, rating) + if isinstance(rating, float) or isinstance(rating, complex): + raise datastore_errors.BadValueError( + 'Expected int or long; received %s (a %s).' % + (rating, typename(rating))) + + try: + if long(rating) < Rating.MIN or long(rating) > Rating.MAX: + raise datastore_errors.BadValueError() + except ValueError: + raise datastore_errors.BadValueError( + 'Expected int or long; received %s (a %s).' % + (rating, typename(rating))) + + def ToXml(self): + return (u'' % + (self, Rating.MIN, Rating.MAX)) + + +class Text(unicode): + """A long string type. + + Strings of any length can be stored in the datastore using this + type. It behaves identically to the Python unicode type, except for + the constructor, which only accepts str and unicode arguments. + """ + + def __new__(cls, arg=None, encoding=None): + """Constructor. + + We only accept unicode and str instances, the latter with encoding. + + Args: + arg: optional unicode or str instance; default u'' + encoding: optional encoding; disallowed when isinstance(arg, unicode), + defaults to 'ascii' when isinstance(arg, str); + """ + if arg is None: + arg = u'' + if isinstance(arg, unicode): + if encoding is not None: + raise TypeError('Text() with a unicode argument ' + 'should not specify an encoding') + return super(Text, cls).__new__(cls, arg) + + if isinstance(arg, str): + if encoding is None: + encoding = 'ascii' + return super(Text, cls).__new__(cls, arg, encoding) + + raise TypeError('Text() argument should be str or unicode, not %s' % + type(arg).__name__) + +class Blob(str): + """A blob type, appropriate for storing binary data of any length. + + This behaves identically to the Python str type, except for the + constructor, which only accepts str arguments. + """ + + def __new__(cls, arg=None): + """Constructor. + + We only accept str instances. + + Args: + arg: optional str instance (default '') + """ + if arg is None: + arg = '' + if isinstance(arg, str): + return super(Blob, cls).__new__(cls, arg) + + raise TypeError('Blob() argument should be str instance, not %s' % + type(arg).__name__) + + def ToXml(self): + """Output a blob as XML. + + Returns: + Base64 encoded version of itself for safe insertion in to an XML document. + """ + encoded = base64.urlsafe_b64encode(self) + return saxutils.escape(encoded) + +class ByteString(str): + """A byte-string type, appropriate for storing short amounts of indexed data. + + This behaves identically to Blob, except it's used only for short, indexed + byte strings. + """ + + def __new__(cls, arg=None): + """Constructor. + + We only accept str instances. + + Args: + arg: optional str instance (default '') + """ + if arg is None: + arg = '' + if isinstance(arg, str): + return super(ByteString, cls).__new__(cls, arg) + + raise TypeError('ByteString() argument should be str instance, not %s' % + type(arg).__name__) + + def ToXml(self): + """Output a ByteString as XML. + + Returns: + Base64 encoded version of itself for safe insertion in to an XML document. + """ + encoded = base64.urlsafe_b64encode(self) + return saxutils.escape(encoded) + + +class BlobKey(object): + """Key used to identify a blob in Blobstore. + + This object wraps a string that gets used internally by the Blobstore API + to identify application blobs. The BlobKey corresponds to the entity name + of the underlying BlobReference entity. + + This class is exposed in the API in both google.appengine.ext.db and + google.appengine.ext.blobstore. + """ + + def __init__(self, blob_key): + """Constructor. + + Used to convert a string to a BlobKey. Normally used internally by + Blobstore API. + + Args: + blob_key: Key name of BlobReference that this key belongs to. + """ + ValidateString(blob_key, 'blob-key') + self.__blob_key = blob_key + + def __str__(self): + """Convert to string.""" + return self.__blob_key + + def __repr__(self): + """Returns an eval()able string representation of this key. + + Returns a Python string of the form 'datastore_types.BlobKey(...)' + that can be used to recreate this key. + + Returns: + string + """ + return 'datastore_types.%s(%r)' % (type(self).__name__, self.__blob_key) + + def __cmp__(self, other): + if type(other) is type(self): + return cmp(str(self), str(other)) + elif isinstance(other, basestring): + return cmp(self.__blob_key, other) + else: + return NotImplemented + + def __hash__(self): + return hash(self.__blob_key) + + def ToXml(self): + return str(self) + + +_PROPERTY_MEANINGS = { + + + + Blob: entity_pb.Property.BLOB, + ByteString: entity_pb.Property.BYTESTRING, + Text: entity_pb.Property.TEXT, + datetime.datetime: entity_pb.Property.GD_WHEN, + Category: entity_pb.Property.ATOM_CATEGORY, + Link: entity_pb.Property.ATOM_LINK, + Email: entity_pb.Property.GD_EMAIL, + GeoPt: entity_pb.Property.GEORSS_POINT, + IM: entity_pb.Property.GD_IM, + PhoneNumber: entity_pb.Property.GD_PHONENUMBER, + PostalAddress: entity_pb.Property.GD_POSTALADDRESS, + Rating: entity_pb.Property.GD_RATING, + BlobKey: entity_pb.Property.BLOBKEY, +} + +_PROPERTY_TYPES = frozenset([ + Blob, + ByteString, + bool, + Category, + datetime.datetime, + Email, + float, + GeoPt, + IM, + int, + Key, + Link, + long, + PhoneNumber, + PostalAddress, + Rating, + str, + Text, + type(None), + unicode, + users.User, + BlobKey, +]) + +_RAW_PROPERTY_TYPES = (Blob, Text) +_RAW_PROPERTY_MEANINGS = (entity_pb.Property.BLOB, entity_pb.Property.TEXT) + +def ValidatePropertyInteger(name, value): + """Raises an exception if the supplied integer is invalid. + + Args: + name: Name of the property this is for. + value: Integer value. + + Raises: + OverflowError if the value does not fit within a signed int64. + """ + if not (-0x8000000000000000 <= value <= 0x7fffffffffffffff): + raise OverflowError('%d is out of bounds for int64' % value) + + +def ValidateStringLength(name, value, max_len): + """Raises an exception if the supplied string is too long. + + Args: + name: Name of the property this is for. + value: String value. + max_len: Maximum length the string may be. + + Raises: + OverflowError if the value is larger than the maximum length. + """ + if len(value) > max_len: + raise datastore_errors.BadValueError( + 'Property %s is %d bytes long; it must be %d or less. ' + 'Consider Text instead, which can store strings of any length.' % + (name, len(value), max_len)) + + +def ValidatePropertyString(name, value): + """Validates the length of an indexed string property. + + Args: + name: Name of the property this is for. + value: String value. + """ + ValidateStringLength(name, value, max_len=_MAX_STRING_LENGTH) + + +def ValidatePropertyLink(name, value): + """Validates the length of an indexed Link property. + + Args: + name: Name of the property this is for. + value: String value. + """ + ValidateStringLength(name, value, max_len=_MAX_LINK_PROPERTY_LENGTH) + + +def ValidatePropertyNothing(name, value): + """No-op validation function. + + Args: + name: Name of the property this is for. + value: Not used. + """ + pass + + +def ValidatePropertyKey(name, value): + """Raises an exception if the supplied datastore.Key instance is invalid. + + Args: + name: Name of the property this is for. + value: A datastore.Key instance. + + Raises: + datastore_errors.BadValueError if the value is invalid. + """ + if not value.has_id_or_name(): + raise datastore_errors.BadValueError( + 'Incomplete key found for reference property %s.' % name) + + +_VALIDATE_PROPERTY_VALUES = { + Blob: ValidatePropertyNothing, + ByteString: ValidatePropertyString, + bool: ValidatePropertyNothing, + Category: ValidatePropertyString, + datetime.datetime: ValidatePropertyNothing, + Email: ValidatePropertyString, + float: ValidatePropertyNothing, + GeoPt: ValidatePropertyNothing, + IM: ValidatePropertyString, + int: ValidatePropertyInteger, + Key: ValidatePropertyKey, + Link: ValidatePropertyLink, + long: ValidatePropertyInteger, + PhoneNumber: ValidatePropertyString, + PostalAddress: ValidatePropertyString, + Rating: ValidatePropertyInteger, + str: ValidatePropertyString, + Text: ValidatePropertyNothing, + type(None): ValidatePropertyNothing, + unicode: ValidatePropertyString, + users.User: ValidatePropertyNothing, + BlobKey: ValidatePropertyNothing, +} + +assert set(_VALIDATE_PROPERTY_VALUES.iterkeys()) == _PROPERTY_TYPES + + +def ValidateProperty(name, values, read_only=False): + """Helper function for validating property values. + + Args: + name: Name of the property this is for. + value: Value for the property as a Python native type. + + Raises: + BadPropertyError if the property name is invalid. BadValueError if the + property did not validate correctly or the value was an empty list. Other + exception types (like OverflowError) if the property value does not meet + type-specific criteria. + """ + ValidateString(name, 'property name', datastore_errors.BadPropertyError) + + if not read_only and RESERVED_PROPERTY_NAME.match(name): + raise datastore_errors.BadPropertyError( + '%s is a reserved property name.' % name) + + values_type = type(values) + + if values_type is tuple: + raise datastore_errors.BadValueError( + 'May not use tuple property value; property %s is %s.' % + (name, repr(values))) + + if values_type is list: + multiple = True + else: + multiple = False + values = [values] + + if not values: + raise datastore_errors.BadValueError( + 'May not use the empty list as a property value; property %s is %s.' % + (name, repr(values))) + + try: + for v in values: + prop_validator = _VALIDATE_PROPERTY_VALUES.get(v.__class__) + if prop_validator is None: + raise datastore_errors.BadValueError( + 'Unsupported type for property %s: %s' % (name, v.__class__)) + prop_validator(name, v) + + except (KeyError, ValueError, TypeError, IndexError, AttributeError), msg: + raise datastore_errors.BadValueError( + 'Error type checking values for property %s: %s' % (name, msg)) + + +ValidateReadProperty = ValidateProperty + + +def PackBlob(name, value, pbvalue): + """Packs a Blob property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A Blob instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_stringvalue(value) + + +def PackString(name, value, pbvalue): + """Packs a string-typed property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A string, unicode, or string-like value instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_stringvalue(unicode(value).encode('utf-8')) + + +def PackDatetime(name, value, pbvalue): + """Packs a datetime-typed property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A datetime.datetime instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_int64value(DatetimeToTimestamp(value)) + + +def DatetimeToTimestamp(value): + """Converts a datetime.datetime to microseconds since the epoch, as a float. + Args: + value: datetime.datetime + + Returns: value as a long + """ + if value.tzinfo: + value = value.astimezone(UTC) + return long(calendar.timegm(value.timetuple()) * 1000000L) + value.microsecond + + +def PackGeoPt(name, value, pbvalue): + """Packs a GeoPt property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A GeoPt instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.mutable_pointvalue().set_x(value.lat) + pbvalue.mutable_pointvalue().set_y(value.lon) + + +def PackUser(name, value, pbvalue): + """Packs a User property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A users.User instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.mutable_uservalue().set_email(value.email().encode('utf-8')) + pbvalue.mutable_uservalue().set_auth_domain( + value.auth_domain().encode('utf-8')) + pbvalue.mutable_uservalue().set_gaiaid(0) + + if value.user_id() is not None: + pbvalue.mutable_uservalue().set_obfuscated_gaiaid( + value.user_id().encode('utf-8')) + + if value.federated_identity() is not None: + pbvalue.mutable_uservalue().set_federated_identity( + value.federated_identity().encode('utf-8')) + + if value.federated_provider() is not None: + pbvalue.mutable_uservalue().set_federated_provider( + value.federated_provider().encode('utf-8')) + + +def PackKey(name, value, pbvalue): + """Packs a reference property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A Key instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + ref = value._Key__reference + pbvalue.mutable_referencevalue().set_app(ref.app()) + SetNamespace(pbvalue.mutable_referencevalue(), ref.name_space()) + for elem in ref.path().element_list(): + pbvalue.mutable_referencevalue().add_pathelement().CopyFrom(elem) + + +def PackBool(name, value, pbvalue): + """Packs a boolean property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A boolean instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_booleanvalue(value) + + +def PackInteger(name, value, pbvalue): + """Packs an integer property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: An int or long instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_int64value(value) + + +def PackFloat(name, value, pbvalue): + """Packs a float property into a entity_pb.PropertyValue. + + Args: + name: The name of the property as a string. + value: A float instance. + pbvalue: The entity_pb.PropertyValue to pack this value into. + """ + pbvalue.set_doublevalue(value) + + +_PACK_PROPERTY_VALUES = { + Blob: PackBlob, + ByteString: PackBlob, + bool: PackBool, + Category: PackString, + datetime.datetime: PackDatetime, + Email: PackString, + float: PackFloat, + GeoPt: PackGeoPt, + IM: PackString, + int: PackInteger, + Key: PackKey, + Link: PackString, + long: PackInteger, + PhoneNumber: PackString, + PostalAddress: PackString, + Rating: PackInteger, + str: PackString, + Text: PackString, + type(None): lambda name, value, pbvalue: None, + unicode: PackString, + users.User: PackUser, + BlobKey: PackString, +} + +assert set(_PACK_PROPERTY_VALUES.iterkeys()) == _PROPERTY_TYPES + + +def ToPropertyPb(name, values): + """Creates type-specific entity_pb.PropertyValues. + + Determines the type and meaning of the PropertyValue based on the Python + type of the input value(s). + + NOTE: This function does not validate anything! + + Args: + name: string or unicode; the property name + values: The values for this property, either a single one or a list of them. + All values must be a supported type. Lists of values must all be of the + same type. + + Returns: + A list of entity_pb.PropertyValue instances. + """ + encoded_name = name.encode('utf-8') + + values_type = type(values) + if values_type is list: + multiple = True + else: + multiple = False + values = [values] + + pbs = [] + for v in values: + pb = entity_pb.Property() + pb.set_name(encoded_name) + pb.set_multiple(multiple) + + meaning = _PROPERTY_MEANINGS.get(v.__class__) + if meaning is not None: + pb.set_meaning(meaning) + + pack_prop = _PACK_PROPERTY_VALUES[v.__class__] + pbvalue = pack_prop(name, v, pb.mutable_value()) + pbs.append(pb) + + if multiple: + return pbs + else: + return pbs[0] + + +def FromReferenceProperty(value): + """Converts a reference PropertyValue to a Key. + + Args: + value: entity_pb.PropertyValue + + Returns: + Key + + Raises: + BadValueError if the value is not a PropertyValue. + """ + assert isinstance(value, entity_pb.PropertyValue) + assert value.has_referencevalue() + ref = value.referencevalue() + + key = Key() + key_ref = key._Key__reference + key_ref.set_app(ref.app()) + SetNamespace(key_ref, ref.name_space()) + + for pathelem in ref.pathelement_list(): + key_ref.mutable_path().add_element().CopyFrom(pathelem) + + return key + + +_EPOCH = datetime.datetime.utcfromtimestamp(0) + +_PROPERTY_CONVERSIONS = { + entity_pb.Property.GD_WHEN: + + + lambda val: _EPOCH + datetime.timedelta(microseconds=val), + entity_pb.Property.ATOM_CATEGORY: Category, + entity_pb.Property.ATOM_LINK: Link, + entity_pb.Property.GD_EMAIL: Email, + entity_pb.Property.GD_IM: IM, + entity_pb.Property.GD_PHONENUMBER: PhoneNumber, + entity_pb.Property.GD_POSTALADDRESS: PostalAddress, + entity_pb.Property.GD_RATING: Rating, + entity_pb.Property.BLOB: Blob, + entity_pb.Property.BYTESTRING: ByteString, + entity_pb.Property.TEXT: Text, + entity_pb.Property.BLOBKEY: BlobKey, +} + + +def FromPropertyPb(pb): + """Converts a property PB to a python value. + + Args: + pb: entity_pb.Property + + Returns: + # return type is determined by the type of the argument + string, int, bool, double, users.User, or one of the atom or gd types + """ + pbval = pb.value() + meaning = pb.meaning() + + if pbval.has_stringvalue(): + value = pbval.stringvalue() + if meaning not in (entity_pb.Property.BLOB, entity_pb.Property.BYTESTRING): + value = unicode(value.decode('utf-8')) + elif pbval.has_int64value(): + value = long(pbval.int64value()) + elif pbval.has_booleanvalue(): + value = bool(pbval.booleanvalue()) + elif pbval.has_doublevalue(): + value = pbval.doublevalue() + elif pbval.has_referencevalue(): + value = FromReferenceProperty(pbval) + elif pbval.has_pointvalue(): + value = GeoPt(pbval.pointvalue().x(), pbval.pointvalue().y()) + elif pbval.has_uservalue(): + email = unicode(pbval.uservalue().email().decode('utf-8')) + auth_domain = unicode(pbval.uservalue().auth_domain().decode('utf-8')) + obfuscated_gaiaid = pbval.uservalue().obfuscated_gaiaid().decode('utf-8') + obfuscated_gaiaid = unicode(obfuscated_gaiaid) + + federated_identity = None + if pbval.uservalue().has_federated_identity(): + federated_identity = unicode( + pbval.uservalue().federated_identity().decode('utf-8')) + + value = users.User(email=email, + _auth_domain=auth_domain, + _user_id=obfuscated_gaiaid, + federated_identity=federated_identity) + else: + value = None + + try: + if pb.has_meaning() and pb.meaning() in _PROPERTY_CONVERSIONS: + conversion = _PROPERTY_CONVERSIONS[meaning] + value = conversion(value) + except (KeyError, ValueError, IndexError, TypeError, AttributeError), msg: + raise datastore_errors.BadValueError( + 'Error converting pb: %s\nException was: %s' % (pb, msg)) + + return value + + +def PropertyTypeName(value): + """Returns the name of the type of the given property value, as a string. + + Raises BadValueError if the value is not a valid property type. + + Args: + value: any valid property value + + Returns: + string + """ + if value.__class__ in _PROPERTY_MEANINGS: + meaning = _PROPERTY_MEANINGS[value.__class__] + name = entity_pb.Property._Meaning_NAMES[meaning] + return name.lower().replace('_', ':') + elif isinstance(value, basestring): + return 'string' + elif isinstance(value, users.User): + return 'user' + elif isinstance(value, long): + return 'int' + elif value is None: + return 'null' + else: + return typename(value).lower() + +_PROPERTY_TYPE_STRINGS = { + 'string': unicode, + 'bool': bool, + 'int': long, + 'null': type(None), + 'float': float, + 'key': Key, + 'blob': Blob, + 'bytestring': ByteString, + 'text': Text, + 'user': users.User, + 'atom:category': Category, + 'atom:link': Link, + 'gd:email': Email, + 'gd:when': datetime.datetime, + 'georss:point': GeoPt, + 'gd:im': IM, + 'gd:phonenumber': PhoneNumber, + 'gd:postaladdress': PostalAddress, + 'gd:rating': Rating, + 'blobkey': BlobKey, + } + + +def FromPropertyTypeName(type_name): + """Returns the python type given a type name. + + Args: + type_name: A string representation of a datastore type name. + + Returns: + A python type. + """ + return _PROPERTY_TYPE_STRINGS[type_name] + + +def PropertyValueFromString(type_, + value_string, + _auth_domain=None): + """Returns an instance of a property value given a type and string value. + + The reverse of this method is just str() and type() of the python value. + + Note that this does *not* support non-UTC offsets in ISO 8601-formatted + datetime strings, e.g. the -08:00 suffix in '2002-12-25 00:00:00-08:00'. + It only supports -00:00 and +00:00 suffixes, which are UTC. + + Args: + type_: A python class. + value_string: A string representation of the value of the property. + + Returns: + An instance of 'type'. + + Raises: + ValueError if type_ is datetime and value_string has a timezone offset. + """ + if type_ == datetime.datetime: + value_string = value_string.strip() + if value_string[-6] in ('+', '-'): + if value_string[-5:] == '00:00': + value_string = value_string[:-6] + else: + raise ValueError('Non-UTC offsets in datetimes are not supported.') + + split = value_string.split('.') + iso_date = split[0] + microseconds = 0 + if len(split) > 1: + microseconds = int(split[1]) + + time_struct = time.strptime(iso_date, '%Y-%m-%d %H:%M:%S')[0:6] + value = datetime.datetime(*(time_struct + (microseconds,))) + return value + elif type_ == Rating: + return Rating(int(value_string)) + elif type_ == bool: + return value_string == 'True' + elif type_ == users.User: + return users.User(value_string, _auth_domain) + elif type_ == type(None): + return None + return type_(value_string) diff --git a/google_appengine/google/appengine/api/datastore_types.pyc b/google_appengine/google/appengine/api/datastore_types.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddc551e7eb63e481bb595ee46a3c9ee5335d28a0 GIT binary patch literal 61731 zcwXhb3vgZ6dLFj+Ie48zfCOKlNQu%WDT<&398#3TD?t<^kc22)5g>d3iV`K>qm8pS zfK48peR%dhBq5Wv$GlB)9ovnasi(2i$<4!=yk@SGahkZXC(|^w<7p>$+9rLbuOx2k zG}9(^-Ap>E`hWkw*4q0#Kv7o$bf+PRjr~~9fBoxU|7-p0@BZ_V;%`0UU#Tkn?*;mO zn||Y89aPF!>IPjM<>%CGN8NDDeNNrTnftuDkvI2!>PDZr?^iea&HaG7F<|Zo)r~=O zKcsF9@qJ$1E~r{j-6$#t&wc9l4pkdgH-_c8U)>&2wVmq5PI(^S=UwW?E_oj0=TUWI zRGx>_?cJ)jN8Q*X&jl`Tue!09o~fFB%HN@G>{mg*3dfW`tR7MK4k&*_-FQm*RL?;b z98$qkDmbWCbNuyb5AWds_L& z&D$63+n1Gp!jv#>-=0zaN%Qta`}P&(zodd!`R#;#`ZegNgX2`ZhuArV4JV z8{bm?Iqqgf`ET*fC(1w1H=go;AKxr1|82ghDu0@9eC1!@n?U&&`DR7=m-uE?`7?a8 z#&D|$+$sXM6@l9)0yodX?Gu69n!v3paQj5ywkmK71a6-Q+*So{p9tJO5xA`i+&&Sw zeZp|_-$7$Cp!-BX=UdQyBA^Qybm3z_=U?W2-BSL$d~;hjKddQ#mXUB>tqm*xn_Q!r zZ)bt!X0n|HUYp5w7HDrK+gYH#nO|pt_-1~c#S}2}>ntXNnO|pt@&?n-VosUqe-^XF zp#E9vpPhzhZ`76l9=AVI{uRDysI`57ia$qbzp4Cr`tz#t0UQH2e}Nv}Q~nRpA0oeH zuG?SaTKy%i&%Z{07^eRF+-v_j_t*b`d+L8ke;8i=4Z0&*`w@5A|Cqb$-{g+^-{LO% z6>hcv3AOi*@;z>56K(OA8F^!@t&8JW)8pTBlzKN@T??9%wcu`0n{eY`-K!^I)%Cr^ zbCZom5W6c;(@oX_eibKCGbp)BYe5{y=c3mP+~9sAs3w8$CXu@wP$`M)#jckm&2YJu zP#MvRJ459rVGx&GcQFXuql>LZBWfbkCDgTq!j2ZhI@dM5Fza4!g?>;f7U!ZkK>X$RAO@XA-RXZrxiC;)Yj6 z)83)Ju2UD?uuk1x_mVKG11iB(vFN&MNz#})b?V-|dnFVQ)V<|e;FqH2>M0-aiyA>Q zJ{51&{jkbePo;kK zP<7$&(!1~&wJS#3zE!D|UaVA}Pl)MMmCEg4qf$wp<@;9MYcy(M;8$wVY9$G&&cs`9 zR9Z15LBU73PLvp%V4|b*sDsmtp&e4CS4s>08$RJYWUoE+FSUvn8t~SF)65b7X zm5FJ23|DAbP1d|R(P!iyr>rL@RD@G|SwH&7z2r4w$eUJGvrZlzMKc`?zm63<%u zIM0-bY2um}W2xo))gZxrd7siWg{xv3i+)hyTRIes9C!eFTtOW`8I>P(cIS95wCc1_ z_*JYJ_#H4~^VgLbm|H_CBEkMapNjffK3~*>M3j*RX}B?JMvZ!eQ9MS8b0F3aK*jKg3EqRexXRYAxa_Vz(XsJYq#tMVqbg>ZevYt@MJ zBHr(U=f97R&VV_P9yC|uDf;UQ1g5ww0S2Y#KGC9B;uOFcNq{?H`K05m;7u2J<7)OW zZ8fdjjo|1I(EG+zvX8v5R<`$5w^?(1fa51N@iAJqe<$6*}Si4Uq*?cw1Jah z?8{FZfv4~`R#0o7x<%9%3H#H8 z{T&HYN&;r)i%^oVo&>~X%ap5|$KGV49;|x2G7;So`~_hMxh9>FN*NDAP$IlR!dg^c zJxQWl51PR<%I_i_R7N-P`2P#(=%fRfdnp-k-A{vNl=+Ou>}N9snIv{pGQ?y;brKUh zLcPdIDL$aO$jNDU9JQZho;`l5JAtXV#FM{#7?)=ad7#>u2w?b`XfS$~kKyt>F2`{> zp)@C=yPZHP$nm98LlvVd2W`#p$KltMirFOZY&XK}?cyD+&CLc|o;I@r;z}(&FqaSq|(( zk<6NhmHbO~LJ_J{856En^<=8F&8CWL_q+}8x^-wPm~v>YmrG`HB?Z%Dp4M&Q`3(A# z&R*)1xx&yxuXEvZNfvjCeu)ZrG%i38{z$E(6)hELTGq z>1>V&QK!`w%O$KwDF<;>yBkb58ngaSD=nj_og>acXKxP~`{X&V z7o@Ns$SF98&c?M)9JFef?*kRPR5&GqY{kAEKlym%vtYn#sb}h~Qrer36&SbS{0(xLL+vr~0oHvRjIjqbX9(n;L%sb%9q@ zyGESR+<-IUbSs0N0ctv`(JvT>3r)=f^yKRQdR zBskWBEEVDLCP)*{xfU^3QV#;3h8E)S{q-7`llE#QBpAd=YgtBvVF}T^6P4=2>_Zh!<;qGe+%zlQARTEA9VV2-LQT^!+Iyf zI%Kn@Q`b)pK0Px0B18Q*>9QCkv1Ne-%&b$u%x8tGyUZF_pntU%cuh%7yjrbu?s5*{ zggP_RxV0KU=FUmZH3#=HwU0Ab#A*$uDyhBrz6ZZtymY?XZB{j_I%4F&!FocFLH7tPh$o{X{Wo50xIiha_6Cw)s|0 zMf*ie$Y}s~YT>fuT7%bn@mduu^m7vrKF$P0ed=%)Lc zSr-XSQ+Hbsiv_M52lU1xcv3oZtC54gvvHz)N-?ZRu1yu>D`DL)AHk>svdo4u>9plB z22i5l{?-mYc+LgWDe3CpQ)Y1=+C}tpe~y`v_kxY8(*Mwn3rO`}sEpA87KeRoW!T52 z2Zvo__ObQBfon`Ks0|P>ln#MUg)yQ4@ewygIK1Fy2RvPHGYl6O+>F4t1vfjDU*wxz zaBacODEwM*vs(qb;R)l9@Z(;tZl|ekAN*SIem`7Wa5JX;THp{Pt}IW%Wd%0}VISGg z5GO*s#Ka>cq}p(A2OF9eSy;Ma?$K(l^!4RyvzJQrh5+XpHHO=&hj&^*jo$dkMnpp7 zVlKk zb;$5xmYU0_4QT)Tj7KoN61X;o3}v|gNib;X$|X5pVHFM4kYm*olM_%EtLy$Lf-wy*5r7I41iHUfP*LDG3F|CGJKDIYoQ+Y|UKpZt zy*{EG2a5+ikXZA9NHac@BjTndVY`lV<7c7wmoJ2jV-NGtgF<9^15#`EjT+M9f3g=p$<-%FmfsuuY|GG%F`~X<)KT$(hp=NU}w`)vUqJ zQdvIxs?TjGvo#CLM>(K|1<;p*u2x-&Pqa zz{|_ghL@}zwW>)VIvWmaVVr0>&?fd`S;RN@qua#LR9>E-#jg@+Hkh7DrP74UpXzO& z>NmBs3|E0lP3aj)H?y{BPmWRsNk#6}jr5~H1m86e)=aHXmU5ZEq?QWI%~`@otc*#Z zSN&F9bgxkbdFIF2@DAkUS&Vm#<=XqDf-V~&Eg)_W6oj8;(lQRxYRi3P6(oPeo6u*C z5D9iHk27gl#B@MCGPN)nD_Tha3GgGl9M;XK=q7CK9Aa~gz~UfHS=Pv^;(#-z2ApST z%DD7NABC=Kz*quN1~CZYHx3240VeBS9)E&z1rv<&~&|98hVWVbB>+;V752i%CTNX?eFB)!_xPDfEou za`I&_mp7>HA9jc(Ar3tY@keWtQqUUpb8IgQ;0bun)Z}V!J|m^98}36Z4_fVNJ*Z? z2d0C+XfB zVp)$*()3>w=mAI&MRS$3ZwjHdzNvq=f0|NlhtP14aLdH(zPh zMD!(b!-KDxIQF^syt>$}AS>U3IRz9qEzgB-qgcybsaeDVHz=)^=;>Pd$^;5#yL~_` zm-TVPC#|d{!W-11*6JGilLYr6%5#A$A&WFNEA^xllthm!>b7xDw4abt!fKeDoXE;J z(tDCNMB78sQ@C8);66fVfizCdExC&!q_5DAQ}rDSG_ANrLe+*FuhSncdE-P=lsN2) z02dUm%H}MHeSi_VUiT;ACOCOFM19;-=%7652!XnmDgj&)*#5#lPNc1*3PTx7r?(ajb>Ww zoh3s?d1mvZ+B1z?5jCuNk5=w(ef)`77v$RFGa6P#Uw<>s0-Cxu5&Pa+M0Jz^IT8vm z+@%nA)Zr3JrCXU^F%1j~(?tPpFV<+4dr_dyl6Q6*#G&q*(fh{)m^jn4HnyZo1rPiQ zwTxg(D5a^}d6vKcoo8DN9f0OWBnvYOl0@VRnl7v>jKODsFOQk)xfeAv)wv-nS?|zg znym?J&kgPpS*W+xm!VQ!>D|*1(7MwKn{y1UsN zO=}GX8?=;yreeTSmQNxXoQ^mzUK3k8ZmKpq@-haxyg-+6)XAt% zAd30s_B}r?2T1Uy?H&L_(}4eyBrJ?F#v+s~G<@P}WV_Gr(ZsD~pCLX&g~ISU4F-A* zRUO*nJeAv%8-$FRJIFHVz8s`dkw!;Y8iiTw1-!|t!cLZq zFPJ!eGHMB?eG3<0?>6}bbFDuPu(PEjH%j3CLO@|zUR*0upyZ9z{NkoSv6QAKT-aEU z0gj!X8JiK%HGO2rZ5PlmrGB~x(2R+Qfp)zEXaXvjhx%0XN(WZu`CCpM6g9|0M@w?h zJM3?9=$9oAcw%5w|XW0g5e(R>UhyP z%iq`(=9uBy4?(>1SkVF%E$dmpChAQ(18q4Gt%WYC{%#80{mw~e zR}biZ>v7PX>V)pePUt?>@d8S`oJQ?@mTtVcDVT+;dJ;fq5q+=+$Sb&j6X}W2|AyLX zghthWwjJb8^`N~I7UY_G6jZdsP>)C2Z$ri*aQvha}(|c=Sb&c5GCZI^<)dH6I zDWH3bk=)wtBCl{c`NfpH?sazbfXiq%Ty)PsfEckPh4X&fOyBNWkoZQ zEyPaEt=ZNk84S>0MfXv?%0KBW)GNyrtBLHaM4~yb&P#eXS@@r&hv&IPFa02gM=v0lkEUA5p(CugwbV}Kfd1!FMG zWAF^|mV2FpaP2XOIAliSJ1T>Stj}~(F3~uGvm<)gr|gcDN0Nzc8KU1M20tudwLd6! z)o_?chc%j#85wqR2FDb)ODh*xyxk|$80}Xdcrp7cG&##XsyLX?r6GTlKmtvK?CNRDAF=BL@``qG z)*ZiAzH-uC+^8qseWUW!BhWr}7;UXogkaK2)~FBbURbwnW=~Uwmo8k|M7_6$>QcM^ z=cL^X2d^NcJO}>s{72^qch28T@yQMDdjQ~s%WBfF9Sg*rhgg!S7jUs)Uhu4lfLWWH017`vq>&YV7dcJlP=lV{$TlA1n}vTtTp zSJaGNwzHJG?yZW7zd=)PDI&mMYlh<|sfo3k_H#^KTexGa?8sWxN-b=xMB9(YrQ`9$ z@tAHtIv(F-UBUFVrFScruFg%*&X2RRCl-j*999N}l083h)oTQ0Ui(>^gq%`-85eAo ze+=ejPNI9sT#Se4oqbjyOfOId`vv zV2xxqvu1%Dv*_itL?vt{k>Xb#Sk~x|sg6~R2H=FHiL1@1)kxPh!HfAQJ1Jx#qoIWY zj$I#@gY1ABgwgsrLF{G}C0v%Sht;Y{FFfL`)4`m~kF+R5250e4S#8`)U8o~;DGtsIU z4(UuzI$lmoqA;l~)=4 z%1XG~1<{2ep^4 zSg+HxcN*$CV)F?q9CR*sZe!B{kBf(JE}2&F6n{6VEv;?Ns!y_y<* zs4PVb%R@P{Koy5snS6xg)4fnOr|J^BMfE4?vhj?T4&Yo#vip#c-4~tf^}VVu5l_cK zxNti4oN_+UD6CjtCA)$|X3)}tUFC+-4Q-U5qEsEOjs>8spVxVePk>Y@ibFI~b}?kI zQ-tEE;oWIerDmQ6_8LaFPMokpQ4tLoB3bhI8lgP&QKCu1EKT!8JBkZPXhl z?!l{XoOC_N5?n(F4FyPlh>hYRw7^4w3v7c`s?z8^!86AA@;jJCP_wg={-RfhOIQ|6 zK{uazqw$aH=WA3Gd1rC?F%W0nXOGK)h%#a*J4Nvfk6w`V$R;1^m0UYCrm2jpqyBhZKFSspw5ixu%9vRrm0rx?>Z7yd zO_t&AEUeDxgNF}1z2&{$+dPc)W$Pjn6+ z_;hPR9EZXEgq|A2uxn|X7f+V9wSu5`#XTlU#`MidLa6lC@h0nqtO!gmT1QlOke~LJ z5fjELy$N?5bEZ=teG`{irI$Kc`BEJ(C4Mzf^gJS#g60W&*OuumpoMxK~@hyo^DgUD~P>gjuC-Bxp& z-@;2a7J%PU&NuXgvU!~LJnZ``JYObHUfn;>K6yEI;mgs~oKzpPALYY-_P^_cFCaWC z2jJRA`EE%}33^y`?);>>PKmH@!YJ&-6Y!Ds68?^p&H%GteuAR~uxedgO6~^YfyN7t zY^u5!>5U?`di@oCuLzt#1IY)u<9vx~Vvu0U^6tF2Vn>!N7h%i~V-5fF;%MDKVXf}}{_A(zI1 zII3^ygw^XCb*WPMAvX>D5g3o5fzxUZa6p)j>bUZ+?zXplt+w#6c*#%PWh%vrnEq6TN!m^*5V>9XF?A)D8t zoYBn`pqrT=N8BOan^MM}K8+H7RIOCR#K&E^VYh2kW)+AHX{Z_~jqR<=?^ui%9v8;sTXi3Ye{e4o~}kjIyCP9H3Qe zAU8Zdd|=m&!XW(&@y}qPzp$flWFS{4?%G$_UpQPiP_|-e#>ok4Q3Rsz zv1co-F4Y>zAOY4d5}Du|93psGkW_gpy&E(Gm>5j-)k!~Sz}jHCioiVCpr@Y(HGkc! zb4*j;3u_yvPQQBkHNO-~@YK|Y8+;&#gFRZ+19H^FgC!X(Ylxb#_JuGu4%mA7^M`<# zNgL9(gdnz<_Fp_E>@m|2G*g=p+M*w^Mp&AW4LHGWj;&>&Cp5q82f*&sl-5QN0JUTq z%szpvMtz`kaKi_(^E}7;RB^1pgKxrzNSI72O+pK@?BAm1m%ppbOvhZyP^dqlwt5bt z)}H`n>fyO!F9`eiT@1;R1TW}He9fB5`XYo`AW&X zrnejLT+{a7374iJQ#nJJ5DR2#!d;;>AanKLUQCbPdW5M1UP=~it#@JPpJ1IxhdYjI z5>2tQyEw!rIyxAp-MAO7Q$hFF8x2ZQgPjoNcW78ZB4)TaDr9!fXimAjz!PWpZkmMB z9#4;uqJto5J?&aESovxQ66$yyF*0osYSb}A5Ho7ENyyrIyo!3eI-`ivv-IcGlh7kL zK462C!&|ToU-|4S?b7Jo;eEVmD&0C zDhuVS3p3@V4|On3<_ucR8Zq&)=&pEsaT~;?Xr`}!Mrp;D;JoaEWCOf-VCvlr-g?Z` z8Y{Rkbt_G8b<6iMa;N_!hyi=OAfi?7h;uj_WX&!|Q0!SEE#%qKX4r4eF^&PHH8JKz z2E~^DG`g^jRQE5SM>+11m{mJy?sk$};MTuw7;})@LV-t`Db1Imws7X3uEW*l%_kVLbwQLF69#Ya(i7kt@gH0D8RCy4lF}?oC52kF!_^SR zFVCm}2e@ouT9^*~qS}N@`%7x8(G<1*a!)Spi3k8KR=qC~|0_0UcV1 z2nZIKcq|8YZ8eBin_gp$Sc#h3h(b7drop30tX!dv!=-JKKSJ&dlNQ^GeBNNmPs~jb z-3U2iT*Kv{ohi~ z#&cR5D25`@zeBSw0gCS2W;?gUVurQ*1-5pJZCfHTTP0y8w$E+le89nq8fh3KI0Ox9 z1^mNYTM#37KOZm_^DeH?s_khaw5Mc)Kqlq&`Xy>^A=BJ5+2#(mH5UO8222118*V`C z_!8HssO=Y(9f}D)X4^dPy_?{D4w=oAM`=7KFpG}5;*6Kr5$_$v2P7+oO74o8hbF4{ za**5$XwuOz^9a9jI$Od@_)Ez73UZoB+ntrrf%DSew#@OxY_l2DM9Y*x$DR%5j~S07 z_6K1jGV3N9wJ<3o+!CwC)43rw;XjYd(MtI3rG;{g?K!H`8;uyfaKc@NL@7*Zum-iCn?4AqK6G^+0tP%t{jd z%SmU$^*iTza)o(D)ggnbi!yht!}$Z=O!mka)UaDX+qU?W3`u6(>tNAJxf4-*vlPST zlfR6M9TPTp%twh;&j)__XWF2iu}}UHr8UnqP%uYq?N~s${7*0z?69L_8trz9x&1UQ z8DDEg!`OrJ zvVXc@)x)o&LlX3k5wt%isLn+!LF@9gt!K{^aaj?+X+HY*!Q8^asc(AA%i4!h zOI%*K&Z?fS^wYC**>qv`PVaQH7k?<5F7ax&vnnK;PD`Up^gWv%ctJ=XG)wDoJ)2{0 zao(LXAA9H6@YbRz&GR81d*{KT{sv|@Krf6iuE_59W(E~Q4TEz4zmz@>+p zE?j@CketPEi_N{I<=#oH*xVsserY}4?0PSG+f;nsJ*V6AIKew>oQ1f(`JChi03usq+4Atv`KQA22gJZ1O@(EB97(6V9CXObhqD4Gs}NVxK(>JvlSK~o zbv!ypGxWCxRkTyYUvUl9@%JE+ayVa+QT^0tg4IlRA#u0^E9j-Zm?E6IN7LaI7$0pD z`WQa?i#QGQ$^;RNrM&WA!6lXLu=ucTcAFUL+R~E$-^EdGx(ub)oBia2elVF=9 zyH~ON!ayyO-5x91jd66EokohouMHHBMeVDS;&@C~a$1}dX-#7JiHI_vk<)&)9lrlQ zEe(L^HUsU9_7qm;|9lHe+mmx52l2=r()!v688K=wOIVO;Jv& z$k3rJj^Ofe`&zAi4r@WZQdvpIY+n4~JH(j(5EouCQX-$qGCjnY@_*FU{;lc+lhp%g zJ+wsw2L_y7IhJO2lPog~S*Fm>GU~!wR1fAko}d+L0B06!T=k@u-M4cv%&fK6z4|1A z1u&Uwpllngu$fvUi`YBZVV`XTP4VU+86lG|7N41QxPANK3h&-a^x7V~lT=bIs(Q5v zR_9U*2`o8*hyg*Igw+Ywd^PKyL`k%dH8;4Q1ob$?QR_=fAHuoj{j0MJ6Yk>NqHM47 zYC7ZHzz8(j^s{)Iz&zo8@J`qiqf$A7ZtZ$lt417n02>c+00g(4h;>N)zh~k$f{b(d z0uDUt{`4|UH_`!mEc4q2>@e=Z^KvX#y?EG?zAR^(!Jo(3sik#~_bvPit<+py#!CHb zO7*r<|GL`hiHKV7r}~gDYNbA=gwAKs;5_Sl+FxZX)<3GYjZ`v4dyvw|uiavealU01 zoPiOxSOW?R5z&R(v`n}!L+5bTT^F5$0|8HJV-sBI^J8Vb=PZxK-{CriM^k3 z1gDqHs9`UXknPPANyHg{m7=?~^dQ@|$5Dh#)3%%@2-B02gTJjdAqRg`Z8e6X)*t`= z$pHd*p6f&bOAy*#evKsIr+$D)0>=7BElCh37Itc8&x3ASfNd8&_eo?oNoa?a0ni1u z3jZwA1$K?Z(8`^mJC! z<^5pIdjV&+mJ5~mK{cc$vp1JkF@TWH{@L@;Pb1(j^X$p%?hj4Bdx2oRPHO)+hU53t%EzZPx( zhGKFBZ?Pd26?vn&IR;ONgq)!A4cVN~&%s@Umigugsd~B}R*2w0N(2{8G3;#{j2$y)@d>EUe z#;L*yQ_o2gJ~92LD-xTK7gxjZMDHS~eJ9`K>@A+2Z|OOMb5c2CUl-Pxjrbfjh0U3K z+=^*6i^^OhyXWb$Iy3Vm$(}(X8}bok1(R5`U%XDR!vQ!>Sw;SiQf32hJ-An?aJ)W= z8v$Cx^Hw&o*J5#!Yp${h#k6m0Nzrdnk-uVBisFb<SURO^*qT0yP=MtZ6YIodFoD(PHa32YL1P{#%G*Fw* zOjANSFFA-rIxl(N@stGWFyhKIP3IyrY8)EpKUG_e(Ww6)0Hb*W*N{v+9CyKNnHgUm z+h>$&lY<}Hy|x)UL5w~BS9MrlL28n8NF?g*4tIf1=XA|Er+0&9ti_Np;cy*{|K19p zwIru)GJAl~BP?k7QI&^5B4LVJ0db_tKYFD+nk_r$TRHEZNbwOgN}2% z2@lZt&n0S@h?a`+P6&2?WeUKxp!5k-CKZN;X@>M)PxAF@+hBhsMC?CTKOjWxUwxHC z>_=3Fh@}ww*OQcu^!?zGGN9e>b&xV}x1b5{1wA6JTvDHBRp{0yYLC!C;}JRpLdQpV zQ864|*KSbLewSv+qj5z`xX~#ciV^03&-hO}(Z^s4BsAGhmM}wFOKiS1W#N8$AYr%V`^g9>f?OsN4=Vrd$qo#;4E` zY}PVqjWidh9u@_XefdMYM`7+`l|gcFo6>6MXBhfRw|~Zj6Tm~*dU41NS>WB8Xu0r&US_Nn6F_EAFH0XWrTFHagqM%ISx zjvnJj#+thC!;kDsMy%AM|JBE;ghxteWyj-jc$Wb#>4LtkLj_-*Gj^y7-Hh7D$`FJ{ znE?aSc8FQEB&T1=!Pt)sYnCx1;3Q))Hs~lLns<|JZ0M*y1&TqyUXaZ8Faz|Qfe8CZ zZo5fp6k8>1@wz-+Ma^tq~ zXbI+D-c6e+jBhWCX^(((+r|9AQg#4sgcICAVIWr+F7y?46ov}Dex+-C@Zz-UYku)_RHXWHcgLw`T}5&0PuW5;oyoqXl&@MV%y{aBwz zOl1gqvQJA}TV#ekkIS*GMur5&viHSZC0Nh^)o}Y^yHFROb*#O@8eE~WwlwrXjt!qL zsPr(Z2koEJ6WR6oRGRPVbDj)AV(T-VL=hW@zC40xu}tF!KCFB9;VnAl&UyF4_13zv zFmY2Pwd|GsBIral*GN=0A!G4jy{&kT#@8fhm=S;}mvSBVS?LgKLoXqt*)o5eV>Z^< zL+PT4BL=0vtHXsE4J?*;VLanrrZaQy-Vt0F?_dg+cNPOVnAO&z;l2zS%BWo7_I(F9 z2>w*<;Nv-R-G=`I$`F3x?|I9ElfkfWXUf*`H3Ab&pS=!5X{3kOAk9~gT?|IOe*V|W zFy;vKwYXHCoxiLjd~NIWA5-1aU)ufib`=}^K4AW1PX=>@{M$x|m;bCX+}1Y1H;dN> zzUcHX^nk7vfk1P8d&&^bBb<*Wqej-PaHUnR+S6OU6wGk&3j_eXoEKnhCxCjbKhz{z zaaX_B14foFL4yeQNKAaBRO3MVy3=!^`5pCS3yp0gqUonP(XWy*nck$5u0bN{Tujl>1LS^3mE3(FZ%gF zPRE=VFvvB!wnQ-`aH5Xp4d^e>e%<*m)%EN3w^Uy;iUU(<9U{OJsaol0_q3zRaUKrZ zG9~9=muuRf4lNXakAVMfP@}=JWQmc?kgLq1s&|Ti`5DBMAiE`9l6x7wh~DjCU5B#s zil&i4ueOVsaz@(sGuPpQ9ETE4Y+LvG}TPK<@yHuW8oGHIQb199?IzK%( z!~QqxUQ(5C{NhXRWx1`6hMC0XD z^?zWGqB9MA-)b55C4f4c`zUQVRGcH)?b_ApsDWAjyxaC&-_=H~txF6_;BRCP=C|6FY~OIOr` zA+p)?j~n3w-6PySBY!(Hu%HVFMDq0vt*O0aZocAS6roig?2}YjU&tw#MZTmfU5b`M zY>oBS8{0py!+J1GV@_p;w#zm)t&CMK+AF8Pi8BmwvPyVw_J^@2Pd;K^oko7&BeAgz zsskDcep0FQtW&SYNZ@#?u-c5`pvs2pI27Y44MZcVu1(0Way-XU8#d@6>nf4CM{NVs zP{vv#8!V6zTY?bvMKVyD)pcH- zam$Pqug;mChz;Y|Ru63X_o&FJej{-W7M;C4)=fAW%UpdV?l6vybKsP zgOO$}|InDzO_K+lp5x4;o36cS3Kn+tag)7M446}pA2@o86#V51atNhO#ha?+b6Yv) z=;Y#N8FwiAxo5`V%`)=glKKG3&Wx$+-QUY#?=_N=wi|i*5T+8>{MBM`$~f>YI|-4& z$Eg~GM%9AP1&zQQZk?mUGjn12O0SiyRpginCSuv=qE55wgX4figq39S1}v(ei#fmql=jXwu_v+d<%*A zgu1tJ;d34B0joS6Jm0UF6T_(;a?E{yHkPVLaJ)ov5N|N9)GEMLsR$u^IVu5>E~#xe$e)FLYSHO(W#_P&FD z{&bH~YB#{9ZhVYeU{1Eb&6xY=YTo*?@16f1nv9Cr8Vanl_IPN#M7$Qh6$e!q6^b@y z<*ackZ;exZZN@1aDc`T||EyS@ehM}woT0|70gg)>f2$iK{5EeQAKp-^FYRHL%E5tWmr)JO@?Arw8<_MO;XT zIJlVd<-bdVbdkMR;|{yWCRi&*s7nD(a-;#!;WS_^z?S57BKH}u+IDJB-b>5b!lcwoZzjM%$jWO8k><<-(cJKkzRA!c>3DiyKi5}Hb_Ve^mB+i~0cuOFW{NUCNS@nni7B~PC?h)h{6k#+5ib827uL1#f~t!wH}Hm! z9wz!^Z6EBKV@)x0Gt=|4^Ow7fF*{5)!4{TXDhtyW-|MvEu#=zRA%%6kNpB=bEz^_# z)igtI0IFax%l`zQ{x^IA% zXtvbuUAbY$m|_Ol1cT?191JnLb0f}EbR9HjVYEpyd{!aTF>S%msm37Fs~>YjXV?ew z(D9BX8EnH6I>l#=5S z^CLutOyFCN37b(wrqWN>>gYj2=~$-qy1EIHcTEc_pobBg)AsfSkw9D>jDqWqgQpcI zbe;*WcZfhUC<-XnOBiAfIwSs6Ft^LM@fmv?OH;gz)-sj!BMH zc4Hl4M26E>VzAg3pLFFM5VYaBlhsd4>;w!(WoF^(#dph06p%{N<1>MJOCeAmqpAi6 z41a-oA^%$cn(yQce<71iZwiq4W;BA)yP{!ZrGqFVM5RwGguY=f5(Ef8s5n4)9&^*G z+9=PR!JJe7c_&rzMPuwlW2dMjtKXL-BR0uSPO>YLK9Yn!`*U@p6yr7a3 zDj8SF$u_T}0%d+tg)bSeqe7?G(SyA4I{Kef8}+qJ(82|wxDD|Fy4yoSYApAnyImyr zm=4%2oE?!=-!O~5*;|?A`OIc$&TCA+A|`T$XEA^S{ZG`MUKX0A(@fD@hR)vEAQ zaT|NXYE~v5ZR%CPqGA-N1)8`LD2$f`y=jphu$JNEQk=2wMfW`I_JSCBLVcfew4?^Y zpK%QkHYa`Ng`<1`sX_8CT)BEd;sAWOG$WqOVsFN7cwRA3UTVP<3m7tFteT@bSyt1+ zrQ-$ny6uZ;Wk#OlR%V{d$FQ;Bgp+AQYLVgNVYm$$D-EdLeZFq+vWP70I&b?VI`4i* zYskBF)xsW*=|Z|S(>zk;ywV^9GI0Bmx}BLceB{Ne^Y71;7iX`|FP8sjRL;IX{|hd> zbDqyPfLtg}o2Y44?TIm08SautBn3Rc0rjBC2I}^ouF$pynvWqBQ{GDMx55ItbCg(gbz`_KT6o zykv>kyzfij@31dfj8(>k6K@8PY(%@MN`;H5mnCnH*Ly#c4&_dk46TNDgNzoI(RHF- z>tp%ICt@M*^6{RwKr6L%v9j1z(vkRXPhQq;Amzzc;p>!~QqEs^PGGK;LCg55-m2Af z)OHrX4gzmeI)o3EIhi=@jr6a7R$DF z27u@p>|dgn@PM7i9AF#J4#D;V^xIE3-p_w=ieUL&H1Abh0H5+Vafc{S!a6h6tVPQ_ zqj~#K3RHv(Eze?=0D;4LGX=7>8Z|fKf_a03)OpX1IZ&DRWYYs8nX$njdq5elP<(g+ z?Xe+3HB9T$7Ey$gDovOYFX7rLDdc%@CRJpq9FRR<+Lqz0UI#5Zw3V zfn8SH5f$$%qDz_He8aVu2_4v8Qiq}Vts^wvG1hPQ}V{hDyK!tS$Dk!avSe}qZrDgHPzQx6 z)<+LP8QF&;*odN1UVn!=0=Agj4RvBajx0Lx6GnH*)Ru_#ExmPnNLhD(`Y&G#{B?(P zVHgkczQ}=Hb;hFhF?gVtfd|Z%`RtZ|OB45<@?MHx9B1|SVlGA8jKNAUuAO4GBeD*W z7^XX$isn2Stk#lYD{9iN* zK3WOtlh>9mQVA=3HfUIPXBV%!ub(~r^5lv+IJoFq*D15vaKa5rt0k^=^7QLdh=kl) zSqbl>fD>mxcuX zsg=DZ{Zu+#IRr_@^r2bZ$C89}S?n;U^dJlELwf?%pXl0v(6Q>Gi7a6l_@S5C*5UY8 z;DdH0(*uECg#Yrh&pzWN^d-JdhS-+ORGFc~8-eD#$Inz1+jG3FHn+qDl~}tW$8v$m zJ^tb3@%72$zI*)LspE4~#}`9hf1z8IJwn&84!!Rf-x1bhh_4fMp&<7`57 zB>1}xNwNkWB-HM!hC`OMnw7b1gBdc#GSpZ$$U>_8lB4V4Lc=(!psQt2U*@q<1=6;v z$<{m5@&2|p{Tc|e_`?1K$Z!c2?;j;rcQ^g)%#9KwTXewG?#mq{rWZ^8Zu;H9M^}Lj z7-uaRM-t%hPXE%ty8JSFhoNW&!Xtc$%b&wrEVB34Yo*w`&!M0auo%*ZOeh`O{xUCB z`+B9Jeu|vIW5WYDnx?B0QZ{ggQJJ-Rj3KZ+^Ug~fcRH*E<^K^cSjKs8=EKV3!pz0l z=__{OOn@GhYxC0!3s+`mE>*5vy3r{N~E+{CkY_ly+urdiDxWj>|Jw%ZrPZg{!mkOFVDr>+Bq} z%yhT#?$!C3`D=3*X39K{cy*Pad}aF5rSi<;B9~4t%hOB5!$^=U68LG%ga?1sGM~(g z+G)zOmgV$zW@u4Hr#yYV^Zi)Yduw+2uTUUw;QeoL`3JcCcewobxWFr1j!2}j=Q*DQ zO`KEBs_gRrp)@MLuZ?WwAl=qe{y&lAcX9c@ae?n^*+HL1a2dsA4=#Ig*^kT9xE#TS zl?wn`E2$FpttZAAyL=L#FW@qR%N#D4Q{^RGDo&Q_Y#HeRD9)`%eybLocTvY5b`W)T z*lGV6&Hj-1Jdoq#rEtO*-5(q%^+(5^8ap($ zXYBp4#j!E^dz60Mu_I%z<@-ip{9&QJ{bw|vcg)X#li&2Gz~6`S!z9X6ZF>qu{@Imx z2K7(T-gW 1: + raise MalformedDosConfiguration('Multiple blacklist: sections ' + 'in configuration.') + return parsed_yaml[0] diff --git a/google_appengine/google/appengine/api/dosinfo.pyc b/google_appengine/google/appengine/api/dosinfo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7660df31de80343f647ad25c722e285a7bbc0dc GIT binary patch literal 3919 zcwV(v?`{)E5T83cwqx=SLTN*RZd)iufP*T81g)wj#HCR~5}gC3u3GDSw~jYC-(GLm zK-3^rg~#ecwBP#>RbQZ;ne$ytp#2tG-t5lq&d$vK_BWG1{+_M>ej(flVEn1$?@Rod zzpx1bI@mf8y0Gs+$I1IHblkjOf=-G1CD<=RGzFb0aA;iSaRoY+Y&^x|Ds-yZxWeNa zbZR(8@ic@us6#jnol_9jp)&*FDUeg3W}%N8X5baTOMve>a}dsgoM-7=AzgrQo|m8I ziG^a~41}jACe9QSix4i-pmUa7X*bT2=Ur^pw%Q`labN9@Qr{{a3#)Zxn)UjI>ZN{q zAo@BLiJux3?}|{H=7B$qL|;YH2tN+RGo9|!)H7uV!VaX7Ld9YzhdMoI*401w*=#s? zlBI}0^&=JXIhkw*8&cTqjZ_rMG?UKe(nuLA<6L|y7wg`h46GrAKQ@29b>sHkyDu)= zX&m+Tyf~C5NL50|*$R0)>c!I9dF-~i`#3X4rv)rr{H@Z`^VmpWC;)?iaqtSk zQ3kKwDAAg)u(>~w!M;JyeJgyCoge}~7Cp&flOcymVYCE$FV?r8-WC{C{Cj7-p&5|A zGXAQhl63!x&7ngW*{gwG1jjm{*oK!5{OUmLpr}MkY#Cmd4-l3q1eaMcwipEbPKfD=gYw_-fzCm>Osbnk6C`ojR@@t!oIaF-v z$~Sc^J;I({z~-Sc^wd@?`qLX($A2C)bB(H5{o@NFoo?qEsr{^%t4{9=tdGAwmftrga>-VMHewY2W zlGKYV zA@rDPdXWzHB{71a7b1-zUKkS)v(vH2WN(y!w8bgKkL(#7bk|z#``-HY&idA7*K0j! zJ>lFlA>|9_$JlUBgtQAqldVtV}6hf@&zg|By8~oVb z5@b)nIMSX>B);{KJz)}C-B`K*&Bl6r$GZ$CidM=Wo1~L2E{k12E)ty(fH_P5LS`Y! zVitBeJz|HWlqK00n2NC)!@TH@lP4xfRfR*~6pT{L#|YjRxSq&!V8IKA!Ci0`SnuCP%=(vvO{vbPp7v+^ab7OX6UGo1#-Yz>tkD-mBjwD|BBRYWo z>4AlLhrYA!j8k=LR5-YecOYjUTEa}7o!}8w+Ox&DS)7PHm2nkCmcz*L_oz7au2S<6 zH6O#6w~Bu?vTQ)2*7HDeCgFfit{M|wc6Fb^N%9=u&gWSz^aTaN2y80l83(`ag2P|8 R;t~T^N=r-U%1gDS>c7Doy8QqE literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/images/__init__.py b/google_appengine/google/appengine/api/images/__init__.py new file mode 100755 index 0000000..c78751e --- /dev/null +++ b/google_appengine/google/appengine/api/images/__init__.py @@ -0,0 +1,978 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Image manipulation API. + +Classes defined in this module: + Image: class used to encapsulate image information and transformations for + that image. + + The current manipulations that are available are resize, rotate, + horizontal_flip, vertical_flip, crop and im_feeling_lucky. + + It should be noted that each transform can only be called once per image + per execute_transforms() call. +""" + + + +import struct + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api.images import images_service_pb +from google.appengine.runtime import apiproxy_errors + + +JPEG = images_service_pb.OutputSettings.JPEG +PNG = images_service_pb.OutputSettings.PNG + +OUTPUT_ENCODING_TYPES = frozenset([JPEG, PNG]) + +TOP_LEFT = images_service_pb.CompositeImageOptions.TOP_LEFT +TOP_CENTER = images_service_pb.CompositeImageOptions.TOP +TOP_RIGHT = images_service_pb.CompositeImageOptions.TOP_RIGHT +CENTER_LEFT = images_service_pb.CompositeImageOptions.LEFT +CENTER_CENTER = images_service_pb.CompositeImageOptions.CENTER +CENTER_RIGHT = images_service_pb.CompositeImageOptions.RIGHT +BOTTOM_LEFT = images_service_pb.CompositeImageOptions.BOTTOM_LEFT +BOTTOM_CENTER = images_service_pb.CompositeImageOptions.BOTTOM +BOTTOM_RIGHT = images_service_pb.CompositeImageOptions.BOTTOM_RIGHT + +ANCHOR_TYPES = frozenset([TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, + CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT, + BOTTOM_CENTER, BOTTOM_RIGHT]) + +MAX_TRANSFORMS_PER_REQUEST = 10 + +MAX_COMPOSITES_PER_REQUEST = 16 + + +class Error(Exception): + """Base error class for this module.""" + + +class TransformationError(Error): + """Error while attempting to transform the image.""" + + +class BadRequestError(Error): + """The parameters given had something wrong with them.""" + + +class NotImageError(Error): + """The image data given is not recognizable as an image.""" + + +class BadImageError(Error): + """The image data given is corrupt.""" + + +class LargeImageError(Error): + """The image data given is too large to process.""" + + +class InvalidBlobKeyError(Error): + """The provided blob key was invalid.""" + + +class BlobKeyRequiredError(Error): + """A blobkey is required for this operation.""" + + +class UnsupportedSizeError(Error): + """Specified size is not supported by requested operation.""" + + +class Image(object): + """Image object to manipulate.""" + + def __init__(self, image_data=None, blob_key=None): + """Constructor. + + Args: + image_data: str, image data in string form. + blob_key: str, image data as a blobstore blob key. + + Raises: + NotImageError if the given data is empty. + """ + if not image_data and not blob_key: + raise NotImageError("Empty image data.") + if image_data and blob_key: + raise NotImageError("Can only take one image or blob key.") + + self._image_data = image_data + self._blob_key = blob_key + self._transforms = [] + self._width = None + self._height = None + + def _check_transform_limits(self): + """Ensure some simple limits on the number of transforms allowed. + + Raises: + BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been + requested for this image + """ + if len(self._transforms) >= MAX_TRANSFORMS_PER_REQUEST: + raise BadRequestError("%d transforms have already been requested on this " + "image." % MAX_TRANSFORMS_PER_REQUEST) + + def _update_dimensions(self): + """Updates the width and height fields of the image. + + Raises: + NotImageError if the image data is not an image. + BadImageError if the image data is corrupt. + """ + if not self._image_data: + raise NotImageError("Dimensions unavailable for blob key input") + size = len(self._image_data) + if size >= 6 and self._image_data.startswith("GIF"): + self._update_gif_dimensions() + elif size >= 8 and self._image_data.startswith("\x89PNG\x0D\x0A\x1A\x0A"): + self._update_png_dimensions() + elif size >= 2 and self._image_data.startswith("\xff\xD8"): + self._update_jpeg_dimensions() + elif (size >= 8 and (self._image_data.startswith("II\x2a\x00") or + self._image_data.startswith("MM\x00\x2a"))): + self._update_tiff_dimensions() + elif size >= 2 and self._image_data.startswith("BM"): + self._update_bmp_dimensions() + elif size >= 4 and self._image_data.startswith("\x00\x00\x01\x00"): + self._update_ico_dimensions() + else: + raise NotImageError("Unrecognized image format") + + def _update_gif_dimensions(self): + """Updates the width and height fields of the gif image. + + Raises: + BadImageError if the image string is not a valid gif image. + """ + size = len(self._image_data) + if size >= 10: + self._width, self._height = struct.unpack("= 24 and self._image_data[12:16] == "IHDR": + self._width, self._height = struct.unpack(">II", self._image_data[16:24]) + else: + raise BadImageError("Corrupt PNG format") + + def _update_jpeg_dimensions(self): + """Updates the width and height fields of the jpeg image. + + Raises: + BadImageError if the image string is not a valid jpeg image. + """ + size = len(self._image_data) + offset = 2 + while offset < size: + while offset < size and ord(self._image_data[offset]) != 0xFF: + offset += 1 + while offset < size and ord(self._image_data[offset]) == 0xFF: + offset += 1 + if (offset < size and ord(self._image_data[offset]) & 0xF0 == 0xC0 and + ord(self._image_data[offset]) != 0xC4): + offset += 4 + if offset + 4 <= size: + self._height, self._width = struct.unpack( + ">HH", + self._image_data[offset:offset + 4]) + break + else: + raise BadImageError("Corrupt JPEG format") + elif offset + 3 <= size: + offset += 1 + offset += struct.unpack(">H", self._image_data[offset:offset + 2])[0] + else: + raise BadImageError("Corrupt JPEG format") + if self._height is None or self._width is None: + raise BadImageError("Corrupt JPEG format") + + def _update_tiff_dimensions(self): + """Updates the width and height fields of the tiff image. + + Raises: + BadImageError if the image string is not a valid tiff image. + """ + size = len(self._image_data) + if self._image_data.startswith("II"): + endianness = "<" + else: + endianness = ">" + ifd_offset = struct.unpack(endianness + "I", self._image_data[4:8])[0] + if ifd_offset + 14 <= size: + ifd_size = struct.unpack( + endianness + "H", + self._image_data[ifd_offset:ifd_offset + 2])[0] + ifd_offset += 2 + for unused_i in range(0, ifd_size): + if ifd_offset + 12 <= size: + tag = struct.unpack( + endianness + "H", + self._image_data[ifd_offset:ifd_offset + 2])[0] + if tag == 0x100 or tag == 0x101: + value_type = struct.unpack( + endianness + "H", + self._image_data[ifd_offset + 2:ifd_offset + 4])[0] + if value_type == 3: + format = endianness + "H" + end_offset = ifd_offset + 10 + elif value_type == 4: + format = endianness + "I" + end_offset = ifd_offset + 12 + else: + format = endianness + "B" + end_offset = ifd_offset + 9 + if tag == 0x100: + self._width = struct.unpack( + format, + self._image_data[ifd_offset + 8:end_offset])[0] + if self._height is not None: + break + else: + self._height = struct.unpack( + format, + self._image_data[ifd_offset + 8:end_offset])[0] + if self._width is not None: + break + ifd_offset += 12 + else: + raise BadImageError("Corrupt TIFF format") + if self._width is None or self._height is None: + raise BadImageError("Corrupt TIFF format") + + def _update_bmp_dimensions(self): + """Updates the width and height fields of the bmp image. + + Raises: + BadImageError if the image string is not a valid bmp image. + """ + size = len(self._image_data) + if size >= 18: + header_length = struct.unpack("= 26): + self._width, self._height = struct.unpack("= 22: + self._width, self._height = struct.unpack("= 8: + self._width, self._height = struct.unpack("= 0.") + + if not width and not height: + raise BadRequestError("At least one of width or height must be > 0.") + + if width > 4000 or height > 4000: + raise BadRequestError("Both width and height must be <= 4000.") + + self._check_transform_limits() + + transform = images_service_pb.Transform() + transform.set_width(width) + transform.set_height(height) + + self._transforms.append(transform) + + def rotate(self, degrees): + """Rotate an image a given number of degrees clockwise. + + Args: + degrees: int, must be a multiple of 90. + + Raises: + TypeError when degrees is not either 'int' or 'long' types. + BadRequestError when there is something wrong with the given degrees or + if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested. + """ + if not isinstance(degrees, (int, long)): + raise TypeError("Degrees must be integers.") + + if degrees % 90 != 0: + raise BadRequestError("degrees argument must be multiple of 90.") + + degrees = degrees % 360 + + self._check_transform_limits() + + transform = images_service_pb.Transform() + transform.set_rotate(degrees) + + self._transforms.append(transform) + + def horizontal_flip(self): + """Flip the image horizontally. + + Raises: + BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been + requested on the image. + """ + self._check_transform_limits() + + transform = images_service_pb.Transform() + transform.set_horizontal_flip(True) + + self._transforms.append(transform) + + def vertical_flip(self): + """Flip the image vertically. + + Raises: + BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already been + requested on the image. + """ + self._check_transform_limits() + transform = images_service_pb.Transform() + transform.set_vertical_flip(True) + + self._transforms.append(transform) + + def _validate_crop_arg(self, val, val_name): + """Validate the given value of a Crop() method argument. + + Args: + val: float, value of the argument. + val_name: str, name of the argument. + + Raises: + TypeError if the args are not of type 'float'. + BadRequestError when there is something wrong with the given bounding box. + """ + if type(val) != float: + raise TypeError("arg '%s' must be of type 'float'." % val_name) + + if not (0 <= val <= 1.0): + raise BadRequestError("arg '%s' must be between 0.0 and 1.0 " + "(inclusive)" % val_name) + + def crop(self, left_x, top_y, right_x, bottom_y): + """Crop the image. + + The four arguments are the scaling numbers to describe the bounding box + which will crop the image. The upper left point of the bounding box will + be at (left_x*image_width, top_y*image_height) the lower right point will + be at (right_x*image_width, bottom_y*image_height). + + Args: + left_x: float value between 0.0 and 1.0 (inclusive). + top_y: float value between 0.0 and 1.0 (inclusive). + right_x: float value between 0.0 and 1.0 (inclusive). + bottom_y: float value between 0.0 and 1.0 (inclusive). + + Raises: + TypeError if the args are not of type 'float'. + BadRequestError when there is something wrong with the given bounding box + or if MAX_TRANSFORMS_PER_REQUEST transforms have already been requested + for this image. + """ + self._validate_crop_arg(left_x, "left_x") + self._validate_crop_arg(top_y, "top_y") + self._validate_crop_arg(right_x, "right_x") + self._validate_crop_arg(bottom_y, "bottom_y") + + if left_x >= right_x: + raise BadRequestError("left_x must be less than right_x") + if top_y >= bottom_y: + raise BadRequestError("top_y must be less than bottom_y") + + self._check_transform_limits() + + transform = images_service_pb.Transform() + transform.set_crop_left_x(left_x) + transform.set_crop_top_y(top_y) + transform.set_crop_right_x(right_x) + transform.set_crop_bottom_y(bottom_y) + + self._transforms.append(transform) + + def im_feeling_lucky(self): + """Automatically adjust image contrast and color levels. + + This is similar to the "I'm Feeling Lucky" button in Picasa. + + Raises: + BadRequestError if MAX_TRANSFORMS_PER_REQUEST transforms have already + been requested for this image. + """ + self._check_transform_limits() + transform = images_service_pb.Transform() + transform.set_autolevels(True) + + self._transforms.append(transform) + + def _set_imagedata(self, imagedata): + """Fills in an ImageData PB from this Image instance. + + Args: + imagedata: An ImageData PB instance + """ + if self._blob_key: + imagedata.set_content("") + imagedata.set_blob_key(self._blob_key) + else: + imagedata.set_content(self._image_data) + + def execute_transforms(self, output_encoding=PNG): + """Perform transformations on given image. + + Args: + output_encoding: A value from OUTPUT_ENCODING_TYPES. + + Returns: + str, image data after the transformations have been performed on it. + + Raises: + BadRequestError when there is something wrong with the request + specifications. + NotImageError when the image data given is not an image. + BadImageError when the image data given is corrupt. + LargeImageError when the image data given is too large to process. + InvalidBlobKeyError when the blob key provided is invalid. + TransformtionError when something errors during image manipulation. + Error when something unknown, but bad, happens. + """ + if output_encoding not in OUTPUT_ENCODING_TYPES: + raise BadRequestError("Output encoding type not in recognized set " + "%s" % OUTPUT_ENCODING_TYPES) + + if not self._transforms: + raise BadRequestError("Must specify at least one transformation.") + + request = images_service_pb.ImagesTransformRequest() + response = images_service_pb.ImagesTransformResponse() + + self._set_imagedata(request.mutable_image()) + + for transform in self._transforms: + request.add_transform().CopyFrom(transform) + + request.mutable_output().set_mime_type(output_encoding) + + try: + apiproxy_stub_map.MakeSyncCall("images", + "Transform", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA): + raise BadRequestError() + elif (e.application_error == + images_service_pb.ImagesServiceError.NOT_IMAGE): + raise NotImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): + raise BadImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): + raise LargeImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.INVALID_BLOB_KEY): + raise InvalidBlobKeyError() + elif (e.application_error == + images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR): + raise TransformationError() + else: + raise Error() + + self._image_data = response.image().content() + self._blob_key = None + self._transforms = [] + self._width = None + self._height = None + return self._image_data + + @property + def width(self): + """Gets the width of the image.""" + if self._width is None: + self._update_dimensions() + return self._width + + @property + def height(self): + """Gets the height of the image.""" + if self._height is None: + self._update_dimensions() + return self._height + + def histogram(self): + """Calculates the histogram of the image. + + Returns: 3 256-element lists containing the number of occurences of each + value of each color in the order RGB. As described at + http://en.wikipedia.org/wiki/Color_histogram for N = 256. i.e. the first + value of the first list contains the number of pixels with a red value of + 0, the second the number with a red value of 1. + + Raises: + NotImageError when the image data given is not an image. + BadImageError when the image data given is corrupt. + LargeImageError when the image data given is too large to process. + Error when something unknown, but bad, happens. + """ + request = images_service_pb.ImagesHistogramRequest() + response = images_service_pb.ImagesHistogramResponse() + + self._set_imagedata(request.mutable_image()) + + try: + apiproxy_stub_map.MakeSyncCall("images", + "Histogram", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + images_service_pb.ImagesServiceError.NOT_IMAGE): + raise NotImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): + raise BadImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): + raise LargeImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.INVALID_BLOB_KEY): + raise InvalidBlobKeyError() + else: + raise Error() + histogram = response.histogram() + return [histogram.red_list(), + histogram.green_list(), + histogram.blue_list()] + + +def resize(image_data, width=0, height=0, output_encoding=PNG): + """Resize a given image file maintaining the aspect ratio. + + If both width and height are specified, the more restricting of the two + values will be used when resizing the photo. The maximum dimension allowed + for both width and height is 4000 pixels. + + Args: + image_data: str, source image data. + width: int, width (in pixels) to change the image width to. + height: int, height (in pixels) to change the image height to. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + TypeError when width or height not either 'int' or 'long' types. + BadRequestError when there is something wrong with the given height or + width. + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.resize(width, height) + return image.execute_transforms(output_encoding=output_encoding) + + +def rotate(image_data, degrees, output_encoding=PNG): + """Rotate a given image a given number of degrees clockwise. + + Args: + image_data: str, source image data. + degrees: value from ROTATE_DEGREE_VALUES. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + TypeError when degrees is not either 'int' or 'long' types. + BadRequestError when there is something wrong with the given degrees. + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.rotate(degrees) + return image.execute_transforms(output_encoding=output_encoding) + + +def horizontal_flip(image_data, output_encoding=PNG): + """Flip the image horizontally. + + Args: + image_data: str, source image data. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.horizontal_flip() + return image.execute_transforms(output_encoding=output_encoding) + + +def vertical_flip(image_data, output_encoding=PNG): + """Flip the image vertically. + + Args: + image_data: str, source image data. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.vertical_flip() + return image.execute_transforms(output_encoding=output_encoding) + + +def crop(image_data, left_x, top_y, right_x, bottom_y, output_encoding=PNG): + """Crop the given image. + + The four arguments are the scaling numbers to describe the bounding box + which will crop the image. The upper left point of the bounding box will + be at (left_x*image_width, top_y*image_height) the lower right point will + be at (right_x*image_width, bottom_y*image_height). + + Args: + image_data: str, source image data. + left_x: float value between 0.0 and 1.0 (inclusive). + top_y: float value between 0.0 and 1.0 (inclusive). + right_x: float value between 0.0 and 1.0 (inclusive). + bottom_y: float value between 0.0 and 1.0 (inclusive). + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + TypeError if the args are not of type 'float'. + BadRequestError when there is something wrong with the given bounding box. + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.crop(left_x, top_y, right_x, bottom_y) + return image.execute_transforms(output_encoding=output_encoding) + + +def im_feeling_lucky(image_data, output_encoding=PNG): + """Automatically adjust image levels. + + This is similar to the "I'm Feeling Lucky" button in Picasa. + + Args: + image_data: str, source image data. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Raises: + Error when something went wrong with the call. See Image.ExecuteTransforms + for more details. + """ + image = Image(image_data) + image.im_feeling_lucky() + return image.execute_transforms(output_encoding=output_encoding) + +def composite(inputs, width, height, color=0, output_encoding=PNG): + """Composite one or more images onto a canvas. + + Args: + inputs: a list of tuples (image_data, x_offset, y_offset, opacity, anchor) + where + image_data: str, source image data. + x_offset: x offset in pixels from the anchor position + y_offset: y offset in piyels from the anchor position + opacity: opacity of the image specified as a float in range [0.0, 1.0] + anchor: anchoring point from ANCHOR_POINTS. The anchor point of the image + is aligned with the same anchor point of the canvas. e.g. TOP_RIGHT would + place the top right corner of the image at the top right corner of the + canvas then apply the x and y offsets. + width: canvas width in pixels. + height: canvas height in pixels. + color: canvas background color encoded as a 32 bit unsigned int where each + color channel is represented by one byte in order ARGB. + output_encoding: a value from OUTPUT_ENCODING_TYPES. + + Returns: + str, image data of the composited image. + + Raises: + TypeError If width, height, color, x_offset or y_offset are not of type + int or long or if opacity is not a float + BadRequestError If more than MAX_TRANSFORMS_PER_REQUEST compositions have + been requested, if the canvas width or height is greater than 4000 or less + than or equal to 0, if the color is invalid or if for any composition + option, the opacity is outside the range [0,1] or the anchor is invalid. + """ + if (not isinstance(width, (int, long)) or + not isinstance(height, (int, long)) or + not isinstance(color, (int, long))): + raise TypeError("Width, height and color must be integers.") + if output_encoding not in OUTPUT_ENCODING_TYPES: + raise BadRequestError("Output encoding type '%s' not in recognized set " + "%s" % (output_encoding, OUTPUT_ENCODING_TYPES)) + + if not inputs: + raise BadRequestError("Must provide at least one input") + if len(inputs) > MAX_COMPOSITES_PER_REQUEST: + raise BadRequestError("A maximum of %d composition operations can be" + "performed in a single request" % + MAX_COMPOSITES_PER_REQUEST) + + if width <= 0 or height <= 0: + raise BadRequestError("Width and height must be > 0.") + if width > 4000 or height > 4000: + raise BadRequestError("Width and height must be <= 4000.") + + if color > 0xffffffff or color < 0: + raise BadRequestError("Invalid color") + if color >= 0x80000000: + color -= 0x100000000 + + image_map = {} + + request = images_service_pb.ImagesCompositeRequest() + response = images_service_pb.ImagesTransformResponse() + for (image, x, y, opacity, anchor) in inputs: + if not image: + raise BadRequestError("Each input must include an image") + if (not isinstance(x, (int, long)) or + not isinstance(y, (int, long)) or + not isinstance(opacity, (float))): + raise TypeError("x_offset, y_offset must be integers and opacity must" + "be a float") + if x > 4000 or x < -4000: + raise BadRequestError("xOffsets must be in range [-4000, 4000]") + if y > 4000 or y < -4000: + raise BadRequestError("yOffsets must be in range [-4000, 4000]") + if opacity < 0 or opacity > 1: + raise BadRequestError("Opacity must be in the range 0.0 to 1.0") + if anchor not in ANCHOR_TYPES: + raise BadRequestError("Anchor type '%s' not in recognized set %s" % + (anchor, ANCHOR_TYPES)) + if image not in image_map: + image_map[image] = request.image_size() + + if isinstance(image, Image): + image._set_imagedata(request.add_image()) + else: + request.add_image().set_content(image) + + option = request.add_options() + option.set_x_offset(x) + option.set_y_offset(y) + option.set_opacity(opacity) + option.set_anchor(anchor) + option.set_source_index(image_map[image]) + + request.mutable_canvas().mutable_output().set_mime_type(output_encoding) + request.mutable_canvas().set_width(width) + request.mutable_canvas().set_height(height) + request.mutable_canvas().set_color(color) + + try: + apiproxy_stub_map.MakeSyncCall("images", + "Composite", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA): + raise BadRequestError() + elif (e.application_error == + images_service_pb.ImagesServiceError.NOT_IMAGE): + raise NotImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): + raise BadImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): + raise LargeImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.INVALID_BLOB_KEY): + raise InvalidBlobKeyError() + elif (e.application_error == + images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR): + raise TransformationError() + else: + raise Error() + + return response.image().content() + + +def histogram(image_data): + """Calculates the histogram of the given image. + + Args: + image_data: str, source image data. + Returns: 3 256-element lists containing the number of occurences of each + value of each color in the order RGB. + + Raises: + NotImageError when the image data given is not an image. + BadImageError when the image data given is corrupt. + LargeImageError when the image data given is too large to process. + Error when something unknown, but bad, happens. + """ + image = Image(image_data) + return image.histogram() + + +IMG_SERVING_SIZES = [ + 32, 48, 64, 72, 80, 90, 94, 104, 110, 120, 128, 144, + 150, 160, 200, 220, 288, 320, 400, 512, 576, 640, 720, + 800, 912, 1024, 1152, 1280, 1440, 1600] + +IMG_SERVING_CROP_SIZES = [32, 48, 64, 72, 80, 104, 136, 144, 150, 160] + + +def get_serving_url(blob_key, + size=None, + crop=False): + """Obtain a url that will serve the underlying image. + + This URL is served by a high-performance dynamic image serving infrastructure. + This URL format also allows dynamic resizing and crop with certain + restrictions. To get dynamic resizing and cropping, specify size and crop + arguments, or simply append options to the end of the default url obtained via + this call. Here is an example: + + get_serving_url -> "http://lh3.ggpht.com/SomeCharactersGoesHere" + + To get a 32 pixel sized version (aspect-ratio preserved) simply append + "=s32" to the url: + + "http://lh3.ggpht.com/SomeCharactersGoesHere=s32" + + To get a 32 pixel cropped version simply append "=s32-c": + + "http://lh3.ggpht.com/SomeCharactersGoesHere=s32-c" + + Available sizes for resize are: + (e.g. "=sX" where X is one of the following values) + + 0, 32, 48, 64, 72, 80, 90, 94, 104, 110, 120, 128, 144, + 150, 160, 200, 220, 288, 320, 400, 512, 576, 640, 720, + 800, 912, 1024, 1152, 1280, 1440, 1600 + + Available sizes for crop are: + (e.g. "=sX-c" where X is one of the following values) + + 32, 48, 64, 72, 80, 104, 136, 144, 150, 160 + + These values are also available as IMG_SERVING_SIZES and + IMG_SERVING_CROP_SIZES integer lists. + + Args: + size: int, size of resulting images + crop: bool, True requests a cropped image, False a resized one. + + Returns: + str, a url + + Raises: + BlobKeyRequiredError: when no blobkey was specified in the ctor. + UnsupportedSizeError: when size parameters uses unsupported sizes. + BadRequestError: when crop/size are present in wrong combination. + """ + if not blob_key: + raise BlobKeyRequiredError("A Blobkey is required for this operation.") + + if crop and not size: + raise BadRequestError("Size should be set for crop operation") + + if size and crop and not size in IMG_SERVING_CROP_SIZES: + raise UnsupportedSizeError("Unsupported crop size") + + if size and not crop and not size in IMG_SERVING_SIZES: + raise UnsupportedSizeError("Unsupported size") + + request = images_service_pb.ImagesGetUrlBaseRequest() + response = images_service_pb.ImagesGetUrlBaseResponse() + + request.set_blob_key(blob_key) + + try: + apiproxy_stub_map.MakeSyncCall("images", + "GetUrlBase", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + images_service_pb.ImagesServiceError.NOT_IMAGE): + raise NotImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA): + raise BadImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.IMAGE_TOO_LARGE): + raise LargeImageError() + elif (e.application_error == + images_service_pb.ImagesServiceError.INVALID_BLOB_KEY): + raise InvalidBlobKeyError() + else: + raise Error() + url = response.url() + + if size: + url += "=s%s" % size + if crop: + url += "-c" + + return url + diff --git a/google_appengine/google/appengine/api/images/__init__.pyc b/google_appengine/google/appengine/api/images/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33a73775666203e534364b32d104d13261ad32c0 GIT binary patch literal 33935 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/images/images_not_implemented_stub.py b/google_appengine/google/appengine/api/images/images_not_implemented_stub.py new file mode 100755 index 0000000..30f6159 --- /dev/null +++ b/google_appengine/google/appengine/api/images/images_not_implemented_stub.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A NotImplemented Images API stub for when the PIL library is not found.""" + + + +class ImagesNotImplementedServiceStub(object): + """Stub version of images API which raises a NotImplementedError.""" + + def MakeSyncCall(self, service, call, request, response): + """Main entry point. + + Args: + service: str, must be 'images'. + call: str, name of the RPC to make, must be part of ImagesService. + request: pb object, corresponding args to the 'call' argument. + response: pb object, return value for the 'call' argument. + """ + raise NotImplementedError("Unable to find the Python PIL library. Please " + "view the SDK documentation for details about " + "installing PIL on your system.") diff --git a/google_appengine/google/appengine/api/images/images_service_pb.py b/google_appengine/google/appengine/api/images/images_service_pb.py new file mode 100755 index 0000000..62bae52 --- /dev/null +++ b/google_appengine/google/appengine/api/images/images_service_pb.py @@ -0,0 +1,2188 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class ImagesServiceError(ProtocolBuffer.ProtocolMessage): + + UNSPECIFIED_ERROR = 1 + BAD_TRANSFORM_DATA = 2 + NOT_IMAGE = 3 + BAD_IMAGE_DATA = 4 + IMAGE_TOO_LARGE = 5 + INVALID_BLOB_KEY = 6 + + _ErrorCode_NAMES = { + 1: "UNSPECIFIED_ERROR", + 2: "BAD_TRANSFORM_DATA", + 3: "NOT_IMAGE", + 4: "BAD_IMAGE_DATA", + 5: "IMAGE_TOO_LARGE", + 6: "INVALID_BLOB_KEY", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesServiceTransform(ProtocolBuffer.ProtocolMessage): + + RESIZE = 1 + ROTATE = 2 + HORIZONTAL_FLIP = 3 + VERTICAL_FLIP = 4 + CROP = 5 + IM_FEELING_LUCKY = 6 + + _Type_NAMES = { + 1: "RESIZE", + 2: "ROTATE", + 3: "HORIZONTAL_FLIP", + 4: "VERTICAL_FLIP", + 5: "CROP", + 6: "IM_FEELING_LUCKY", + } + + def Type_Name(cls, x): return cls._Type_NAMES.get(x, "") + Type_Name = classmethod(Type_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Transform(ProtocolBuffer.ProtocolMessage): + has_width_ = 0 + width_ = 0 + has_height_ = 0 + height_ = 0 + has_rotate_ = 0 + rotate_ = 0 + has_horizontal_flip_ = 0 + horizontal_flip_ = 0 + has_vertical_flip_ = 0 + vertical_flip_ = 0 + has_crop_left_x_ = 0 + crop_left_x_ = 0.0 + has_crop_top_y_ = 0 + crop_top_y_ = 0.0 + has_crop_right_x_ = 0 + crop_right_x_ = 1.0 + has_crop_bottom_y_ = 0 + crop_bottom_y_ = 1.0 + has_autolevels_ = 0 + autolevels_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def width(self): return self.width_ + + def set_width(self, x): + self.has_width_ = 1 + self.width_ = x + + def clear_width(self): + if self.has_width_: + self.has_width_ = 0 + self.width_ = 0 + + def has_width(self): return self.has_width_ + + def height(self): return self.height_ + + def set_height(self, x): + self.has_height_ = 1 + self.height_ = x + + def clear_height(self): + if self.has_height_: + self.has_height_ = 0 + self.height_ = 0 + + def has_height(self): return self.has_height_ + + def rotate(self): return self.rotate_ + + def set_rotate(self, x): + self.has_rotate_ = 1 + self.rotate_ = x + + def clear_rotate(self): + if self.has_rotate_: + self.has_rotate_ = 0 + self.rotate_ = 0 + + def has_rotate(self): return self.has_rotate_ + + def horizontal_flip(self): return self.horizontal_flip_ + + def set_horizontal_flip(self, x): + self.has_horizontal_flip_ = 1 + self.horizontal_flip_ = x + + def clear_horizontal_flip(self): + if self.has_horizontal_flip_: + self.has_horizontal_flip_ = 0 + self.horizontal_flip_ = 0 + + def has_horizontal_flip(self): return self.has_horizontal_flip_ + + def vertical_flip(self): return self.vertical_flip_ + + def set_vertical_flip(self, x): + self.has_vertical_flip_ = 1 + self.vertical_flip_ = x + + def clear_vertical_flip(self): + if self.has_vertical_flip_: + self.has_vertical_flip_ = 0 + self.vertical_flip_ = 0 + + def has_vertical_flip(self): return self.has_vertical_flip_ + + def crop_left_x(self): return self.crop_left_x_ + + def set_crop_left_x(self, x): + self.has_crop_left_x_ = 1 + self.crop_left_x_ = x + + def clear_crop_left_x(self): + if self.has_crop_left_x_: + self.has_crop_left_x_ = 0 + self.crop_left_x_ = 0.0 + + def has_crop_left_x(self): return self.has_crop_left_x_ + + def crop_top_y(self): return self.crop_top_y_ + + def set_crop_top_y(self, x): + self.has_crop_top_y_ = 1 + self.crop_top_y_ = x + + def clear_crop_top_y(self): + if self.has_crop_top_y_: + self.has_crop_top_y_ = 0 + self.crop_top_y_ = 0.0 + + def has_crop_top_y(self): return self.has_crop_top_y_ + + def crop_right_x(self): return self.crop_right_x_ + + def set_crop_right_x(self, x): + self.has_crop_right_x_ = 1 + self.crop_right_x_ = x + + def clear_crop_right_x(self): + if self.has_crop_right_x_: + self.has_crop_right_x_ = 0 + self.crop_right_x_ = 1.0 + + def has_crop_right_x(self): return self.has_crop_right_x_ + + def crop_bottom_y(self): return self.crop_bottom_y_ + + def set_crop_bottom_y(self, x): + self.has_crop_bottom_y_ = 1 + self.crop_bottom_y_ = x + + def clear_crop_bottom_y(self): + if self.has_crop_bottom_y_: + self.has_crop_bottom_y_ = 0 + self.crop_bottom_y_ = 1.0 + + def has_crop_bottom_y(self): return self.has_crop_bottom_y_ + + def autolevels(self): return self.autolevels_ + + def set_autolevels(self, x): + self.has_autolevels_ = 1 + self.autolevels_ = x + + def clear_autolevels(self): + if self.has_autolevels_: + self.has_autolevels_ = 0 + self.autolevels_ = 0 + + def has_autolevels(self): return self.has_autolevels_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_width()): self.set_width(x.width()) + if (x.has_height()): self.set_height(x.height()) + if (x.has_rotate()): self.set_rotate(x.rotate()) + if (x.has_horizontal_flip()): self.set_horizontal_flip(x.horizontal_flip()) + if (x.has_vertical_flip()): self.set_vertical_flip(x.vertical_flip()) + if (x.has_crop_left_x()): self.set_crop_left_x(x.crop_left_x()) + if (x.has_crop_top_y()): self.set_crop_top_y(x.crop_top_y()) + if (x.has_crop_right_x()): self.set_crop_right_x(x.crop_right_x()) + if (x.has_crop_bottom_y()): self.set_crop_bottom_y(x.crop_bottom_y()) + if (x.has_autolevels()): self.set_autolevels(x.autolevels()) + + def Equals(self, x): + if x is self: return 1 + if self.has_width_ != x.has_width_: return 0 + if self.has_width_ and self.width_ != x.width_: return 0 + if self.has_height_ != x.has_height_: return 0 + if self.has_height_ and self.height_ != x.height_: return 0 + if self.has_rotate_ != x.has_rotate_: return 0 + if self.has_rotate_ and self.rotate_ != x.rotate_: return 0 + if self.has_horizontal_flip_ != x.has_horizontal_flip_: return 0 + if self.has_horizontal_flip_ and self.horizontal_flip_ != x.horizontal_flip_: return 0 + if self.has_vertical_flip_ != x.has_vertical_flip_: return 0 + if self.has_vertical_flip_ and self.vertical_flip_ != x.vertical_flip_: return 0 + if self.has_crop_left_x_ != x.has_crop_left_x_: return 0 + if self.has_crop_left_x_ and self.crop_left_x_ != x.crop_left_x_: return 0 + if self.has_crop_top_y_ != x.has_crop_top_y_: return 0 + if self.has_crop_top_y_ and self.crop_top_y_ != x.crop_top_y_: return 0 + if self.has_crop_right_x_ != x.has_crop_right_x_: return 0 + if self.has_crop_right_x_ and self.crop_right_x_ != x.crop_right_x_: return 0 + if self.has_crop_bottom_y_ != x.has_crop_bottom_y_: return 0 + if self.has_crop_bottom_y_ and self.crop_bottom_y_ != x.crop_bottom_y_: return 0 + if self.has_autolevels_ != x.has_autolevels_: return 0 + if self.has_autolevels_ and self.autolevels_ != x.autolevels_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_width_): n += 1 + self.lengthVarInt64(self.width_) + if (self.has_height_): n += 1 + self.lengthVarInt64(self.height_) + if (self.has_rotate_): n += 1 + self.lengthVarInt64(self.rotate_) + if (self.has_horizontal_flip_): n += 2 + if (self.has_vertical_flip_): n += 2 + if (self.has_crop_left_x_): n += 5 + if (self.has_crop_top_y_): n += 5 + if (self.has_crop_right_x_): n += 5 + if (self.has_crop_bottom_y_): n += 5 + if (self.has_autolevels_): n += 2 + return n + 0 + + def Clear(self): + self.clear_width() + self.clear_height() + self.clear_rotate() + self.clear_horizontal_flip() + self.clear_vertical_flip() + self.clear_crop_left_x() + self.clear_crop_top_y() + self.clear_crop_right_x() + self.clear_crop_bottom_y() + self.clear_autolevels() + + def OutputUnchecked(self, out): + if (self.has_width_): + out.putVarInt32(8) + out.putVarInt32(self.width_) + if (self.has_height_): + out.putVarInt32(16) + out.putVarInt32(self.height_) + if (self.has_rotate_): + out.putVarInt32(24) + out.putVarInt32(self.rotate_) + if (self.has_horizontal_flip_): + out.putVarInt32(32) + out.putBoolean(self.horizontal_flip_) + if (self.has_vertical_flip_): + out.putVarInt32(40) + out.putBoolean(self.vertical_flip_) + if (self.has_crop_left_x_): + out.putVarInt32(53) + out.putFloat(self.crop_left_x_) + if (self.has_crop_top_y_): + out.putVarInt32(61) + out.putFloat(self.crop_top_y_) + if (self.has_crop_right_x_): + out.putVarInt32(69) + out.putFloat(self.crop_right_x_) + if (self.has_crop_bottom_y_): + out.putVarInt32(77) + out.putFloat(self.crop_bottom_y_) + if (self.has_autolevels_): + out.putVarInt32(80) + out.putBoolean(self.autolevels_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_width(d.getVarInt32()) + continue + if tt == 16: + self.set_height(d.getVarInt32()) + continue + if tt == 24: + self.set_rotate(d.getVarInt32()) + continue + if tt == 32: + self.set_horizontal_flip(d.getBoolean()) + continue + if tt == 40: + self.set_vertical_flip(d.getBoolean()) + continue + if tt == 53: + self.set_crop_left_x(d.getFloat()) + continue + if tt == 61: + self.set_crop_top_y(d.getFloat()) + continue + if tt == 69: + self.set_crop_right_x(d.getFloat()) + continue + if tt == 77: + self.set_crop_bottom_y(d.getFloat()) + continue + if tt == 80: + self.set_autolevels(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_width_: res+=prefix+("width: %s\n" % self.DebugFormatInt32(self.width_)) + if self.has_height_: res+=prefix+("height: %s\n" % self.DebugFormatInt32(self.height_)) + if self.has_rotate_: res+=prefix+("rotate: %s\n" % self.DebugFormatInt32(self.rotate_)) + if self.has_horizontal_flip_: res+=prefix+("horizontal_flip: %s\n" % self.DebugFormatBool(self.horizontal_flip_)) + if self.has_vertical_flip_: res+=prefix+("vertical_flip: %s\n" % self.DebugFormatBool(self.vertical_flip_)) + if self.has_crop_left_x_: res+=prefix+("crop_left_x: %s\n" % self.DebugFormatFloat(self.crop_left_x_)) + if self.has_crop_top_y_: res+=prefix+("crop_top_y: %s\n" % self.DebugFormatFloat(self.crop_top_y_)) + if self.has_crop_right_x_: res+=prefix+("crop_right_x: %s\n" % self.DebugFormatFloat(self.crop_right_x_)) + if self.has_crop_bottom_y_: res+=prefix+("crop_bottom_y: %s\n" % self.DebugFormatFloat(self.crop_bottom_y_)) + if self.has_autolevels_: res+=prefix+("autolevels: %s\n" % self.DebugFormatBool(self.autolevels_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kwidth = 1 + kheight = 2 + krotate = 3 + khorizontal_flip = 4 + kvertical_flip = 5 + kcrop_left_x = 6 + kcrop_top_y = 7 + kcrop_right_x = 8 + kcrop_bottom_y = 9 + kautolevels = 10 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "width", + 2: "height", + 3: "rotate", + 4: "horizontal_flip", + 5: "vertical_flip", + 6: "crop_left_x", + 7: "crop_top_y", + 8: "crop_right_x", + 9: "crop_bottom_y", + 10: "autolevels", + }, 10) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.FLOAT, + 7: ProtocolBuffer.Encoder.FLOAT, + 8: ProtocolBuffer.Encoder.FLOAT, + 9: ProtocolBuffer.Encoder.FLOAT, + 10: ProtocolBuffer.Encoder.NUMERIC, + }, 10, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImageData(ProtocolBuffer.ProtocolMessage): + has_content_ = 0 + content_ = "" + has_blob_key_ = 0 + blob_key_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def content(self): return self.content_ + + def set_content(self, x): + self.has_content_ = 1 + self.content_ = x + + def clear_content(self): + if self.has_content_: + self.has_content_ = 0 + self.content_ = "" + + def has_content(self): return self.has_content_ + + def blob_key(self): return self.blob_key_ + + def set_blob_key(self, x): + self.has_blob_key_ = 1 + self.blob_key_ = x + + def clear_blob_key(self): + if self.has_blob_key_: + self.has_blob_key_ = 0 + self.blob_key_ = "" + + def has_blob_key(self): return self.has_blob_key_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_content()): self.set_content(x.content()) + if (x.has_blob_key()): self.set_blob_key(x.blob_key()) + + def Equals(self, x): + if x is self: return 1 + if self.has_content_ != x.has_content_: return 0 + if self.has_content_ and self.content_ != x.content_: return 0 + if self.has_blob_key_ != x.has_blob_key_: return 0 + if self.has_blob_key_ and self.blob_key_ != x.blob_key_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_content_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: content not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.content_)) + if (self.has_blob_key_): n += 1 + self.lengthString(len(self.blob_key_)) + return n + 1 + + def Clear(self): + self.clear_content() + self.clear_blob_key() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.content_) + if (self.has_blob_key_): + out.putVarInt32(18) + out.putPrefixedString(self.blob_key_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_content(d.getPrefixedString()) + continue + if tt == 18: + self.set_blob_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_content_: res+=prefix+("content: %s\n" % self.DebugFormatString(self.content_)) + if self.has_blob_key_: res+=prefix+("blob_key: %s\n" % self.DebugFormatString(self.blob_key_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcontent = 1 + kblob_key = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "content", + 2: "blob_key", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class OutputSettings(ProtocolBuffer.ProtocolMessage): + + PNG = 0 + JPEG = 1 + + _MIME_TYPE_NAMES = { + 0: "PNG", + 1: "JPEG", + } + + def MIME_TYPE_Name(cls, x): return cls._MIME_TYPE_NAMES.get(x, "") + MIME_TYPE_Name = classmethod(MIME_TYPE_Name) + + has_mime_type_ = 0 + mime_type_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def mime_type(self): return self.mime_type_ + + def set_mime_type(self, x): + self.has_mime_type_ = 1 + self.mime_type_ = x + + def clear_mime_type(self): + if self.has_mime_type_: + self.has_mime_type_ = 0 + self.mime_type_ = 0 + + def has_mime_type(self): return self.has_mime_type_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_mime_type()): self.set_mime_type(x.mime_type()) + + def Equals(self, x): + if x is self: return 1 + if self.has_mime_type_ != x.has_mime_type_: return 0 + if self.has_mime_type_ and self.mime_type_ != x.mime_type_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_mime_type_): n += 1 + self.lengthVarInt64(self.mime_type_) + return n + 0 + + def Clear(self): + self.clear_mime_type() + + def OutputUnchecked(self, out): + if (self.has_mime_type_): + out.putVarInt32(8) + out.putVarInt32(self.mime_type_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_mime_type(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_mime_type_: res+=prefix+("mime_type: %s\n" % self.DebugFormatInt32(self.mime_type_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kmime_type = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "mime_type", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesTransformRequest(ProtocolBuffer.ProtocolMessage): + has_image_ = 0 + has_output_ = 0 + + def __init__(self, contents=None): + self.image_ = ImageData() + self.transform_ = [] + self.output_ = OutputSettings() + if contents is not None: self.MergeFromString(contents) + + def image(self): return self.image_ + + def mutable_image(self): self.has_image_ = 1; return self.image_ + + def clear_image(self):self.has_image_ = 0; self.image_.Clear() + + def has_image(self): return self.has_image_ + + def transform_size(self): return len(self.transform_) + def transform_list(self): return self.transform_ + + def transform(self, i): + return self.transform_[i] + + def mutable_transform(self, i): + return self.transform_[i] + + def add_transform(self): + x = Transform() + self.transform_.append(x) + return x + + def clear_transform(self): + self.transform_ = [] + def output(self): return self.output_ + + def mutable_output(self): self.has_output_ = 1; return self.output_ + + def clear_output(self):self.has_output_ = 0; self.output_.Clear() + + def has_output(self): return self.has_output_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_image()): self.mutable_image().MergeFrom(x.image()) + for i in xrange(x.transform_size()): self.add_transform().CopyFrom(x.transform(i)) + if (x.has_output()): self.mutable_output().MergeFrom(x.output()) + + def Equals(self, x): + if x is self: return 1 + if self.has_image_ != x.has_image_: return 0 + if self.has_image_ and self.image_ != x.image_: return 0 + if len(self.transform_) != len(x.transform_): return 0 + for e1, e2 in zip(self.transform_, x.transform_): + if e1 != e2: return 0 + if self.has_output_ != x.has_output_: return 0 + if self.has_output_ and self.output_ != x.output_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_image_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: image not set.') + elif not self.image_.IsInitialized(debug_strs): initialized = 0 + for p in self.transform_: + if not p.IsInitialized(debug_strs): initialized=0 + if (not self.has_output_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: output not set.') + elif not self.output_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.image_.ByteSize()) + n += 1 * len(self.transform_) + for i in xrange(len(self.transform_)): n += self.lengthString(self.transform_[i].ByteSize()) + n += self.lengthString(self.output_.ByteSize()) + return n + 2 + + def Clear(self): + self.clear_image() + self.clear_transform() + self.clear_output() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.image_.ByteSize()) + self.image_.OutputUnchecked(out) + for i in xrange(len(self.transform_)): + out.putVarInt32(18) + out.putVarInt32(self.transform_[i].ByteSize()) + self.transform_[i].OutputUnchecked(out) + out.putVarInt32(26) + out.putVarInt32(self.output_.ByteSize()) + self.output_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_image().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_transform().TryMerge(tmp) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_output().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_image_: + res+=prefix+"image <\n" + res+=self.image_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.transform_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("transform%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_output_: + res+=prefix+"output <\n" + res+=self.output_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kimage = 1 + ktransform = 2 + koutput = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "image", + 2: "transform", + 3: "output", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesTransformResponse(ProtocolBuffer.ProtocolMessage): + has_image_ = 0 + + def __init__(self, contents=None): + self.image_ = ImageData() + if contents is not None: self.MergeFromString(contents) + + def image(self): return self.image_ + + def mutable_image(self): self.has_image_ = 1; return self.image_ + + def clear_image(self):self.has_image_ = 0; self.image_.Clear() + + def has_image(self): return self.has_image_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_image()): self.mutable_image().MergeFrom(x.image()) + + def Equals(self, x): + if x is self: return 1 + if self.has_image_ != x.has_image_: return 0 + if self.has_image_ and self.image_ != x.image_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_image_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: image not set.') + elif not self.image_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.image_.ByteSize()) + return n + 1 + + def Clear(self): + self.clear_image() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.image_.ByteSize()) + self.image_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_image().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_image_: + res+=prefix+"image <\n" + res+=self.image_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kimage = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "image", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompositeImageOptions(ProtocolBuffer.ProtocolMessage): + + TOP_LEFT = 0 + TOP = 1 + TOP_RIGHT = 2 + LEFT = 3 + CENTER = 4 + RIGHT = 5 + BOTTOM_LEFT = 6 + BOTTOM = 7 + BOTTOM_RIGHT = 8 + + _ANCHOR_NAMES = { + 0: "TOP_LEFT", + 1: "TOP", + 2: "TOP_RIGHT", + 3: "LEFT", + 4: "CENTER", + 5: "RIGHT", + 6: "BOTTOM_LEFT", + 7: "BOTTOM", + 8: "BOTTOM_RIGHT", + } + + def ANCHOR_Name(cls, x): return cls._ANCHOR_NAMES.get(x, "") + ANCHOR_Name = classmethod(ANCHOR_Name) + + has_source_index_ = 0 + source_index_ = 0 + has_x_offset_ = 0 + x_offset_ = 0 + has_y_offset_ = 0 + y_offset_ = 0 + has_opacity_ = 0 + opacity_ = 0.0 + has_anchor_ = 0 + anchor_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def source_index(self): return self.source_index_ + + def set_source_index(self, x): + self.has_source_index_ = 1 + self.source_index_ = x + + def clear_source_index(self): + if self.has_source_index_: + self.has_source_index_ = 0 + self.source_index_ = 0 + + def has_source_index(self): return self.has_source_index_ + + def x_offset(self): return self.x_offset_ + + def set_x_offset(self, x): + self.has_x_offset_ = 1 + self.x_offset_ = x + + def clear_x_offset(self): + if self.has_x_offset_: + self.has_x_offset_ = 0 + self.x_offset_ = 0 + + def has_x_offset(self): return self.has_x_offset_ + + def y_offset(self): return self.y_offset_ + + def set_y_offset(self, x): + self.has_y_offset_ = 1 + self.y_offset_ = x + + def clear_y_offset(self): + if self.has_y_offset_: + self.has_y_offset_ = 0 + self.y_offset_ = 0 + + def has_y_offset(self): return self.has_y_offset_ + + def opacity(self): return self.opacity_ + + def set_opacity(self, x): + self.has_opacity_ = 1 + self.opacity_ = x + + def clear_opacity(self): + if self.has_opacity_: + self.has_opacity_ = 0 + self.opacity_ = 0.0 + + def has_opacity(self): return self.has_opacity_ + + def anchor(self): return self.anchor_ + + def set_anchor(self, x): + self.has_anchor_ = 1 + self.anchor_ = x + + def clear_anchor(self): + if self.has_anchor_: + self.has_anchor_ = 0 + self.anchor_ = 0 + + def has_anchor(self): return self.has_anchor_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_source_index()): self.set_source_index(x.source_index()) + if (x.has_x_offset()): self.set_x_offset(x.x_offset()) + if (x.has_y_offset()): self.set_y_offset(x.y_offset()) + if (x.has_opacity()): self.set_opacity(x.opacity()) + if (x.has_anchor()): self.set_anchor(x.anchor()) + + def Equals(self, x): + if x is self: return 1 + if self.has_source_index_ != x.has_source_index_: return 0 + if self.has_source_index_ and self.source_index_ != x.source_index_: return 0 + if self.has_x_offset_ != x.has_x_offset_: return 0 + if self.has_x_offset_ and self.x_offset_ != x.x_offset_: return 0 + if self.has_y_offset_ != x.has_y_offset_: return 0 + if self.has_y_offset_ and self.y_offset_ != x.y_offset_: return 0 + if self.has_opacity_ != x.has_opacity_: return 0 + if self.has_opacity_ and self.opacity_ != x.opacity_: return 0 + if self.has_anchor_ != x.has_anchor_: return 0 + if self.has_anchor_ and self.anchor_ != x.anchor_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_source_index_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: source_index not set.') + if (not self.has_x_offset_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: x_offset not set.') + if (not self.has_y_offset_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: y_offset not set.') + if (not self.has_opacity_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: opacity not set.') + if (not self.has_anchor_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: anchor not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.source_index_) + n += self.lengthVarInt64(self.x_offset_) + n += self.lengthVarInt64(self.y_offset_) + n += self.lengthVarInt64(self.anchor_) + return n + 9 + + def Clear(self): + self.clear_source_index() + self.clear_x_offset() + self.clear_y_offset() + self.clear_opacity() + self.clear_anchor() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.source_index_) + out.putVarInt32(16) + out.putVarInt32(self.x_offset_) + out.putVarInt32(24) + out.putVarInt32(self.y_offset_) + out.putVarInt32(37) + out.putFloat(self.opacity_) + out.putVarInt32(40) + out.putVarInt32(self.anchor_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_source_index(d.getVarInt32()) + continue + if tt == 16: + self.set_x_offset(d.getVarInt32()) + continue + if tt == 24: + self.set_y_offset(d.getVarInt32()) + continue + if tt == 37: + self.set_opacity(d.getFloat()) + continue + if tt == 40: + self.set_anchor(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_source_index_: res+=prefix+("source_index: %s\n" % self.DebugFormatInt32(self.source_index_)) + if self.has_x_offset_: res+=prefix+("x_offset: %s\n" % self.DebugFormatInt32(self.x_offset_)) + if self.has_y_offset_: res+=prefix+("y_offset: %s\n" % self.DebugFormatInt32(self.y_offset_)) + if self.has_opacity_: res+=prefix+("opacity: %s\n" % self.DebugFormatFloat(self.opacity_)) + if self.has_anchor_: res+=prefix+("anchor: %s\n" % self.DebugFormatInt32(self.anchor_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + ksource_index = 1 + kx_offset = 2 + ky_offset = 3 + kopacity = 4 + kanchor = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "source_index", + 2: "x_offset", + 3: "y_offset", + 4: "opacity", + 5: "anchor", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.FLOAT, + 5: ProtocolBuffer.Encoder.NUMERIC, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesCanvas(ProtocolBuffer.ProtocolMessage): + has_width_ = 0 + width_ = 0 + has_height_ = 0 + height_ = 0 + has_output_ = 0 + has_color_ = 0 + color_ = -1 + + def __init__(self, contents=None): + self.output_ = OutputSettings() + if contents is not None: self.MergeFromString(contents) + + def width(self): return self.width_ + + def set_width(self, x): + self.has_width_ = 1 + self.width_ = x + + def clear_width(self): + if self.has_width_: + self.has_width_ = 0 + self.width_ = 0 + + def has_width(self): return self.has_width_ + + def height(self): return self.height_ + + def set_height(self, x): + self.has_height_ = 1 + self.height_ = x + + def clear_height(self): + if self.has_height_: + self.has_height_ = 0 + self.height_ = 0 + + def has_height(self): return self.has_height_ + + def output(self): return self.output_ + + def mutable_output(self): self.has_output_ = 1; return self.output_ + + def clear_output(self):self.has_output_ = 0; self.output_.Clear() + + def has_output(self): return self.has_output_ + + def color(self): return self.color_ + + def set_color(self, x): + self.has_color_ = 1 + self.color_ = x + + def clear_color(self): + if self.has_color_: + self.has_color_ = 0 + self.color_ = -1 + + def has_color(self): return self.has_color_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_width()): self.set_width(x.width()) + if (x.has_height()): self.set_height(x.height()) + if (x.has_output()): self.mutable_output().MergeFrom(x.output()) + if (x.has_color()): self.set_color(x.color()) + + def Equals(self, x): + if x is self: return 1 + if self.has_width_ != x.has_width_: return 0 + if self.has_width_ and self.width_ != x.width_: return 0 + if self.has_height_ != x.has_height_: return 0 + if self.has_height_ and self.height_ != x.height_: return 0 + if self.has_output_ != x.has_output_: return 0 + if self.has_output_ and self.output_ != x.output_: return 0 + if self.has_color_ != x.has_color_: return 0 + if self.has_color_ and self.color_ != x.color_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_width_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: width not set.') + if (not self.has_height_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: height not set.') + if (not self.has_output_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: output not set.') + elif not self.output_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.width_) + n += self.lengthVarInt64(self.height_) + n += self.lengthString(self.output_.ByteSize()) + if (self.has_color_): n += 1 + self.lengthVarInt64(self.color_) + return n + 3 + + def Clear(self): + self.clear_width() + self.clear_height() + self.clear_output() + self.clear_color() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.width_) + out.putVarInt32(16) + out.putVarInt32(self.height_) + out.putVarInt32(26) + out.putVarInt32(self.output_.ByteSize()) + self.output_.OutputUnchecked(out) + if (self.has_color_): + out.putVarInt32(32) + out.putVarInt32(self.color_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_width(d.getVarInt32()) + continue + if tt == 16: + self.set_height(d.getVarInt32()) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_output().TryMerge(tmp) + continue + if tt == 32: + self.set_color(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_width_: res+=prefix+("width: %s\n" % self.DebugFormatInt32(self.width_)) + if self.has_height_: res+=prefix+("height: %s\n" % self.DebugFormatInt32(self.height_)) + if self.has_output_: + res+=prefix+"output <\n" + res+=self.output_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_color_: res+=prefix+("color: %s\n" % self.DebugFormatInt32(self.color_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kwidth = 1 + kheight = 2 + koutput = 3 + kcolor = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "width", + 2: "height", + 3: "output", + 4: "color", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesCompositeRequest(ProtocolBuffer.ProtocolMessage): + has_canvas_ = 0 + + def __init__(self, contents=None): + self.image_ = [] + self.options_ = [] + self.canvas_ = ImagesCanvas() + if contents is not None: self.MergeFromString(contents) + + def image_size(self): return len(self.image_) + def image_list(self): return self.image_ + + def image(self, i): + return self.image_[i] + + def mutable_image(self, i): + return self.image_[i] + + def add_image(self): + x = ImageData() + self.image_.append(x) + return x + + def clear_image(self): + self.image_ = [] + def options_size(self): return len(self.options_) + def options_list(self): return self.options_ + + def options(self, i): + return self.options_[i] + + def mutable_options(self, i): + return self.options_[i] + + def add_options(self): + x = CompositeImageOptions() + self.options_.append(x) + return x + + def clear_options(self): + self.options_ = [] + def canvas(self): return self.canvas_ + + def mutable_canvas(self): self.has_canvas_ = 1; return self.canvas_ + + def clear_canvas(self):self.has_canvas_ = 0; self.canvas_.Clear() + + def has_canvas(self): return self.has_canvas_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.image_size()): self.add_image().CopyFrom(x.image(i)) + for i in xrange(x.options_size()): self.add_options().CopyFrom(x.options(i)) + if (x.has_canvas()): self.mutable_canvas().MergeFrom(x.canvas()) + + def Equals(self, x): + if x is self: return 1 + if len(self.image_) != len(x.image_): return 0 + for e1, e2 in zip(self.image_, x.image_): + if e1 != e2: return 0 + if len(self.options_) != len(x.options_): return 0 + for e1, e2 in zip(self.options_, x.options_): + if e1 != e2: return 0 + if self.has_canvas_ != x.has_canvas_: return 0 + if self.has_canvas_ and self.canvas_ != x.canvas_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.image_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.options_: + if not p.IsInitialized(debug_strs): initialized=0 + if (not self.has_canvas_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: canvas not set.') + elif not self.canvas_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.image_) + for i in xrange(len(self.image_)): n += self.lengthString(self.image_[i].ByteSize()) + n += 1 * len(self.options_) + for i in xrange(len(self.options_)): n += self.lengthString(self.options_[i].ByteSize()) + n += self.lengthString(self.canvas_.ByteSize()) + return n + 1 + + def Clear(self): + self.clear_image() + self.clear_options() + self.clear_canvas() + + def OutputUnchecked(self, out): + for i in xrange(len(self.image_)): + out.putVarInt32(10) + out.putVarInt32(self.image_[i].ByteSize()) + self.image_[i].OutputUnchecked(out) + for i in xrange(len(self.options_)): + out.putVarInt32(18) + out.putVarInt32(self.options_[i].ByteSize()) + self.options_[i].OutputUnchecked(out) + out.putVarInt32(26) + out.putVarInt32(self.canvas_.ByteSize()) + self.canvas_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_image().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_options().TryMerge(tmp) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_canvas().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.image_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("image%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + cnt=0 + for e in self.options_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("options%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_canvas_: + res+=prefix+"canvas <\n" + res+=self.canvas_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kimage = 1 + koptions = 2 + kcanvas = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "image", + 2: "options", + 3: "canvas", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesCompositeResponse(ProtocolBuffer.ProtocolMessage): + has_image_ = 0 + + def __init__(self, contents=None): + self.image_ = ImageData() + if contents is not None: self.MergeFromString(contents) + + def image(self): return self.image_ + + def mutable_image(self): self.has_image_ = 1; return self.image_ + + def clear_image(self):self.has_image_ = 0; self.image_.Clear() + + def has_image(self): return self.has_image_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_image()): self.mutable_image().MergeFrom(x.image()) + + def Equals(self, x): + if x is self: return 1 + if self.has_image_ != x.has_image_: return 0 + if self.has_image_ and self.image_ != x.image_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_image_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: image not set.') + elif not self.image_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.image_.ByteSize()) + return n + 1 + + def Clear(self): + self.clear_image() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.image_.ByteSize()) + self.image_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_image().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_image_: + res+=prefix+"image <\n" + res+=self.image_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kimage = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "image", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesHistogramRequest(ProtocolBuffer.ProtocolMessage): + has_image_ = 0 + + def __init__(self, contents=None): + self.image_ = ImageData() + if contents is not None: self.MergeFromString(contents) + + def image(self): return self.image_ + + def mutable_image(self): self.has_image_ = 1; return self.image_ + + def clear_image(self):self.has_image_ = 0; self.image_.Clear() + + def has_image(self): return self.has_image_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_image()): self.mutable_image().MergeFrom(x.image()) + + def Equals(self, x): + if x is self: return 1 + if self.has_image_ != x.has_image_: return 0 + if self.has_image_ and self.image_ != x.image_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_image_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: image not set.') + elif not self.image_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.image_.ByteSize()) + return n + 1 + + def Clear(self): + self.clear_image() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.image_.ByteSize()) + self.image_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_image().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_image_: + res+=prefix+"image <\n" + res+=self.image_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kimage = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "image", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesHistogram(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.red_ = [] + self.green_ = [] + self.blue_ = [] + if contents is not None: self.MergeFromString(contents) + + def red_size(self): return len(self.red_) + def red_list(self): return self.red_ + + def red(self, i): + return self.red_[i] + + def set_red(self, i, x): + self.red_[i] = x + + def add_red(self, x): + self.red_.append(x) + + def clear_red(self): + self.red_ = [] + + def green_size(self): return len(self.green_) + def green_list(self): return self.green_ + + def green(self, i): + return self.green_[i] + + def set_green(self, i, x): + self.green_[i] = x + + def add_green(self, x): + self.green_.append(x) + + def clear_green(self): + self.green_ = [] + + def blue_size(self): return len(self.blue_) + def blue_list(self): return self.blue_ + + def blue(self, i): + return self.blue_[i] + + def set_blue(self, i, x): + self.blue_[i] = x + + def add_blue(self, x): + self.blue_.append(x) + + def clear_blue(self): + self.blue_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.red_size()): self.add_red(x.red(i)) + for i in xrange(x.green_size()): self.add_green(x.green(i)) + for i in xrange(x.blue_size()): self.add_blue(x.blue(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.red_) != len(x.red_): return 0 + for e1, e2 in zip(self.red_, x.red_): + if e1 != e2: return 0 + if len(self.green_) != len(x.green_): return 0 + for e1, e2 in zip(self.green_, x.green_): + if e1 != e2: return 0 + if len(self.blue_) != len(x.blue_): return 0 + for e1, e2 in zip(self.blue_, x.blue_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.red_) + for i in xrange(len(self.red_)): n += self.lengthVarInt64(self.red_[i]) + n += 1 * len(self.green_) + for i in xrange(len(self.green_)): n += self.lengthVarInt64(self.green_[i]) + n += 1 * len(self.blue_) + for i in xrange(len(self.blue_)): n += self.lengthVarInt64(self.blue_[i]) + return n + 0 + + def Clear(self): + self.clear_red() + self.clear_green() + self.clear_blue() + + def OutputUnchecked(self, out): + for i in xrange(len(self.red_)): + out.putVarInt32(8) + out.putVarInt32(self.red_[i]) + for i in xrange(len(self.green_)): + out.putVarInt32(16) + out.putVarInt32(self.green_[i]) + for i in xrange(len(self.blue_)): + out.putVarInt32(24) + out.putVarInt32(self.blue_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.add_red(d.getVarInt32()) + continue + if tt == 16: + self.add_green(d.getVarInt32()) + continue + if tt == 24: + self.add_blue(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.red_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("red%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + cnt=0 + for e in self.green_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("green%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + cnt=0 + for e in self.blue_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("blue%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kred = 1 + kgreen = 2 + kblue = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "red", + 2: "green", + 3: "blue", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesHistogramResponse(ProtocolBuffer.ProtocolMessage): + has_histogram_ = 0 + + def __init__(self, contents=None): + self.histogram_ = ImagesHistogram() + if contents is not None: self.MergeFromString(contents) + + def histogram(self): return self.histogram_ + + def mutable_histogram(self): self.has_histogram_ = 1; return self.histogram_ + + def clear_histogram(self):self.has_histogram_ = 0; self.histogram_.Clear() + + def has_histogram(self): return self.has_histogram_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_histogram()): self.mutable_histogram().MergeFrom(x.histogram()) + + def Equals(self, x): + if x is self: return 1 + if self.has_histogram_ != x.has_histogram_: return 0 + if self.has_histogram_ and self.histogram_ != x.histogram_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_histogram_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: histogram not set.') + elif not self.histogram_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.histogram_.ByteSize()) + return n + 1 + + def Clear(self): + self.clear_histogram() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.histogram_.ByteSize()) + self.histogram_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_histogram().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_histogram_: + res+=prefix+"histogram <\n" + res+=self.histogram_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + khistogram = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "histogram", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesGetUrlBaseRequest(ProtocolBuffer.ProtocolMessage): + has_blob_key_ = 0 + blob_key_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def blob_key(self): return self.blob_key_ + + def set_blob_key(self, x): + self.has_blob_key_ = 1 + self.blob_key_ = x + + def clear_blob_key(self): + if self.has_blob_key_: + self.has_blob_key_ = 0 + self.blob_key_ = "" + + def has_blob_key(self): return self.has_blob_key_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_blob_key()): self.set_blob_key(x.blob_key()) + + def Equals(self, x): + if x is self: return 1 + if self.has_blob_key_ != x.has_blob_key_: return 0 + if self.has_blob_key_ and self.blob_key_ != x.blob_key_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_blob_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: blob_key not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.blob_key_)) + return n + 1 + + def Clear(self): + self.clear_blob_key() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.blob_key_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_blob_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_blob_key_: res+=prefix+("blob_key: %s\n" % self.DebugFormatString(self.blob_key_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kblob_key = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "blob_key", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ImagesGetUrlBaseResponse(ProtocolBuffer.ProtocolMessage): + has_url_ = 0 + url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def url(self): return self.url_ + + def set_url(self, x): + self.has_url_ = 1 + self.url_ = x + + def clear_url(self): + if self.has_url_: + self.has_url_ = 0 + self.url_ = "" + + def has_url(self): return self.has_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_url()): self.set_url(x.url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_url_ != x.has_url_: return 0 + if self.has_url_ and self.url_ != x.url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.url_)) + return n + 1 + + def Clear(self): + self.clear_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_url_: res+=prefix+("url: %s\n" % self.DebugFormatString(self.url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kurl = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['ImagesServiceError','ImagesServiceTransform','Transform','ImageData','OutputSettings','ImagesTransformRequest','ImagesTransformResponse','CompositeImageOptions','ImagesCanvas','ImagesCompositeRequest','ImagesCompositeResponse','ImagesHistogramRequest','ImagesHistogram','ImagesHistogramResponse','ImagesGetUrlBaseRequest','ImagesGetUrlBaseResponse'] diff --git a/google_appengine/google/appengine/api/images/images_service_pb.pyc b/google_appengine/google/appengine/api/images/images_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a9bf3cf54ac15523233740a8305de98aef3e886 GIT binary patch literal 102845 zcwX&&3w&M2S?8Sl)RnFtmTbwgWm|qFPU6^k%6T|W{E%%mw(K4&aUz=D$~wpPjU`=K z_bQdUPJudIx@>{&_6H@9l0Zwh3++NF4sDC|y|S7AS=B*lg*xJO6La zJkH}vy0UzZlE|_~=P_qyzM1d)=KH?+=9|xarM3AN`%U+^5*36dO%3?>fy!GtA*>mN^;bTCAL7=Pl-B*>j9tW9-S%Jacl!i@bW# z#GEGMMM1qNFsIpg(V|{7GpA_0XjLy-nA2vwXjd!=u$7*n6uV+ zu}-~cXHK{AqDQ^xV9t8uMX!3XhB+II7aP@!PUiF(FE*(cUCh~Ryx5{%tYuEW@nWlb zv5q<0j2GM0i*Aeu?|I(G4)q)FA>J1pY7QTVgtwWuj<%=#GAVb8cpAYW$*$ zyQ#|U<6{#O<*9P5xj+1Gc51fjI#XklJoEHSd8#%z;Z6?EPM&jTc02C5*$bs=ZKjG+ zs?+Xxd2FKA$?F@M9J}CF&$u(sl*iq{nVHIr!rKi0Jp{bn%3JI*pex5v;3-GlG@vr^ zbTkJH<*0cvM}5TD07p!obpYL!;(rRrJb<3d@INicJ^+j@qJuexx*S6b?3#sZ^drYb zeoeiM46Pm#0-ZLX-|@EHL%>lQ23)N1FmIGb1s7dFISdcSFW%<0^!a-Fd>wPwv5SOZ zJi`G0y~cBNdr#dSR$kA_>YaDLfnDQ3Z$zo1eUxru_L*H9F~AtxatcWr3A(VHMUumy zui?pK!)Hzp9v(V6G2|{LnjX& z8zg)|Cb|BK>sXY0VT;l~f27&=lqbmG*Z(xZb5@t-5mFqkIBse9;K{)=HI#C}t%)4C8K0=u0QcN3`VvX? zEKl}ctW3Ilr^aW-DtnJq-a0i=8FQ+8FH|ZQCfw54^t3y5p*-b!5BBN@W7FlmWkO5u zPpK-VnQubtlf(nownBs^(7BsxbkNDoZ8sfKq8MDnnyf zsx<=i^#b&(1p0KIS$t&Q;Rjf;qJvdlO|4v+qK+jnZH&HMlu+Gx&(-Dpu}YSMWU(`p2oxqV6s=AK5)k{~<=L@`>ONGJBQRQ7R)7MUu_P$Xj0A#bQ|xLg zqWNjr{2FCcY`oUOZ-E7zkC)%(Ixzv(%F7C)M7BtQ?s?{+1g`}U&b%F&e1^I)6BpWnRdaIK#HUu=yiF zBY2Twl~&HqQkZ2qM*|l}t_n!zzfod3KY#>x{!Kztf|d~9ZDs&@BX zsGrq(Sx_oF;*RrKhPWajP4!ZF`p8&q%%Dv4wo{{-A+Fv}pze5#<++&nUpax29*@^W`}j&VW!!2sPuzHSy&cCO*#l zN~P1xBv>>9!#9bS$ zvezMjlwxl{0s-3Igao>hjjplpU|z?UMh2f45k!hg2B$!lGju)t*vUbA=rFbTZIGve~h$de}qYhCKHbokWp$l&k@<vDO&9X**{BBLM+S*W9zSIdjdIjFc%pRl#L#Jym>wUrM}`i|Cn)N$ed@H()lQa< z4i26e8a`G!@z~)<8`aeW+oq`pv{o9qGVQ5pf-w=H;!T{=FX&%1gfov>)P^dTK{XTn zqg??;V*Q%f03*(Ig|TJOx$G`K5b=!;ao)GHuoS64SmDYQ%^Kf|N|r;dvH`8#6JP6r zAn#;sxs)cmH@>E0lKxkrrmr+ziNuKSU||GboSJ0!#n)2EI`0XzGy$c^hP-EQL4s#? zKa#D?M<(Fa_V)O?1`^2ay~?f-bXA@pKr3LR6)@5Y7-AH)q*HX%5`bVKrK#uQ$PVpRn zxZYdu9PNb?f{P8_LMH_FCc5bJRyeuX&Xo~dZ1$EmN4H3Aq~BZ99Nj9_k8M)@*iO0! zzt|ylkDXHYxJl|BH%r}Pm$z;?dJB{YT-@p{SdQLCN(jH$?X6Uf-VV(K7kj)V%F(@8 z7R1FJ-rD5oomeHr#a-T_b5c@Sj{IEPT!fO8n7 z4LCPr=gGFlI&|2$Eqa(jeV4{WRxqKDynWxH00AvjY>fa`wG4tOpZ#Wo3um$sUsm( zxMs+k1k`aP1Y_L8CJd79*o*-1w@{x@Bb!YS7eG7$AojLnXcUfohDo$rWShxSdeJRk zxLAvyT5_uH-&5_r9&%~%y269UJ|W-4PA?!_(2HovXW5M*m|-LYWZWZ%t`r0#Ap8Ti zG>REcqVbZeil%>_Ern!eDz&kin+(Z_y1$^>y&NRt!DHLcz@T`E8Y{+So+t{6u*=e%fgdH{#0*lItO& zwO;Q8A+(>6Sz@OqEF{nb3K zHA-ku+Ma>S-gshU)*5WWR}~c3Lu8#^7x|&H-z+o4PGZo7L1MlJeU0510(%P*f;H~p z1ZoNb^8pIDmO@@{Ndg88RS0*oR-?p4Q}|3}x-{XQua)NFC#@EL1M&)j>mjTTuX9Yu zY)8J1ovbiRgRG3^6s#o?RVqkSdJ#lf%mj?*kr2pnk0>xxP?rI3>k?_}d=e0lROLsX z&W0&VA$E=bUWuQuntk=JT^73Xb&P<^ep~5I8K%-Q`xk{~8nM`8ZY7I7F z&$3XMRJHuj+0V!fv6GlowR{b_<3`ii91;RL?s3&Rmk;85mPllCNxTWy zhGVm}%7pujJ5i0F#F~BeKe#OPB_%@xF8iu{8#{4H$xyfEp`}sQf<_GZ7Yjh+9z`t> zYA6pbk(%C~1OQSX)XhIy9~HmFk{IasHdo%El^)^$K|X}#G#*JW5aAnY^K_eedMMBOV4hwx z&j!k~F_@>%%(IE|Y&P>a8e_&~2pd}~Y+#0c8Ogsug6#?RTakP#l5a-xEK7*vkR#?0 z&=Um|o;rqnXU=BeOsfg$D%d|%| z!5kSTh$sA9eko`v8_JRMXe|6(ewk;2=z=)}>nIP?84nf~-5zpn9?H&gR=ICI85rm)1F9ly%6Ux!)@r&?t`DGUq48+Xg zXs2lqvY5okf%rHQcqwW33%#nW7s9l~URxKnMMUdwTFNx0$3&I)h>!n_yhMWKyGhglE{d@h_r~RNK(_Z6!V^_8DI}~QUH6J*=FOnt?qAF3cbjr z6v`zGOJIV$N~x=~y2_}lth&mntGv2uQdcCmnL-PQ(tZPy(@4M%>^C7fhXf6>r;s2d zko`6!-@@XTiD~Z!MBVCI>g49oPgr312}`LiTbU;hJHiE8+$fs040_dBQ-Q1*U)HRd zH66&B^JUGOSu-kY6Y~bd$l7dX%?7fz__7wwtT~mnm9n<^vbLL9^MR}#zN~A^tWANe zoxZGHX4XO=>snvdb!OJ)09f6ASsF%wVg8Dm#vrxxq#p^etc<-nWo!y64=iWDfv$d;~~ zEn{ZO)XkPPvjvn{BU{eQmam&FZ)OXqy+*bsGh1`rYy~r0OWkbEX0~G8Y%ONC*190* zJ)v!21IC`vj_{sP$e-SX-8lKd1ZeCcCFPe?UQ%?S-V&ub&WoYF%xdP{}Zr-$XPc~^b%%YkdxpOiS73Oj9)R>wy59N z{jTsYpx08=YliNK76+p6l+i3(^M(K9!GzZ1fG$F*&kXZ#H z_f_jvwz<4BrL4ORY%Zq`5V4)(cSgwT%GaUwsPMWx5x^nINyCH4ejj7WEI$yNB_AKr zlpZ}gjrbtU*L{D*o}4LG3I3SkZ@CZ0SV%f+24!ghmgF4Om6@#T-YQ#1gtVpM+wv^9S`2FXpxJK~2s%tIuj?J0jRN11C zkj2l;$%yf&o@S#~oM9poyr<4Ovp4QrnYV&A&lMuJ)ZKYEpqjWwOZkYsB`RwFGQtf zSBr%b+Ds;92C)bM*;`6cVhMss;d$Bf#yUYXDP&ZqC$q}pDR_!wy< zhTL*y#2y+xmb8`ldwAJJbnGMe47JvE z7;3x8R%?;1R)m9H$WZGj`)oIT-X+7+l9kr&AyC!|P}UJB4js%e+Uvb^H(*0Sxlfp6 z)s4vL-ESf*>}J%5h_!1FuGT(7E3BG=Nv1nJe2fl`c*E(zV@meG3t}5k=mdKf>Wx$9 zj0o_+(#fHdgLLXi;s-?lkBrugO}g&_uA<)8i0Sz|wanOn=@}z3S)O!DHJqXmx3EhL zPF4SJ>W@eR<}@Dwb+JGgt-*iDVi3DI`CMIn33Se>~y#YDx1L%!5>W$V{?@p6{uUSphPLl!F@J6EfMnc~K^tD2Huri%G*MVAx`^VOxxW z-qd$!sbPj8c8#|y#@6z4=+92-PoJ%&zR#~D{sv7gY1uo%d;&UB)21+&XgqrtlX#X< zos*bN>(&YPzPA3 zkZl^WeV~IbDTG>y$K`y4m;HA1+!5v-my=4%<8e|xb4Qw|xuP_9G`Hu9X=&t;CXT?F zn(EjbtX;X)+Boz6fqo7FviDe#r{|Ds)pNp@ks`A~PCB6FrOBg7I-nJ#U87mrHCm)y zqbThftVV{Uro zF*h4<%nb($XWWp-89wi&&--x94G!PgNJfl47%|*Fb}@r9ZX8%fHY3M?vjtfOoPOjP z;J>hw^m|#|t*pF_mA4B!iMZK8pXr;8=8Y)AyWdF`l$+2-oYm8VgL>>UyTI^?=EHL~ zk{V55LVLR)E>f_wcn>pSLqo2Pp@G)ZVPzwuAEBo>Ew?5Sr$qQk9$gi(nUz+8Vd76x zOfJ$Cxi(mAz&v69EQ?RUgUd zU~4%JBw>h_6Dj{NlAlYW`Q$L6cuuwaHuuXR7#8aH70~dBa<%q1s^KB#-J%-K*>P{U z@tU8;C%F;kCA5^E|~@0!Hj;P(o*a zzz<@4_9NoAV~(T2`zL@Wl-+{W5n=Pi7oKFmn6nb-M4NOK6#pC;q{Yzq&nMA#^t2Fu z{$G8@x~SP(Iwng|DiLr<#r7yhG~^o*vz!8~!TI~s%p0a;=5KPpAUqSGS?R_n@J}bv za4Fb{W~bBUB!Z63$VOw5o`1n$lQdoN8_(@CV=f!a4PYl29!o#lBTsFU&XN>)MTHEw zDqa7q%Wq_7@vPabqQ$7f7c$S_Ip9TNW#Usveh~?&@Sj9NR*e6IJ44ejUlLF=krDFw!wjG}Pu?+M{%_rm@NDVma0?S2r@u7^c5q%e%T!6T?Gh zp_*C?^5>bmm(?;}HZKFrOqe(2MS!;?G?%jVmlkMU3k<8dr05W()uAX3#DR{uLYFNPmtk z!P)pRPA8kCrmS)FS*!zY(A25Q3A(x^B8%W@+EKbmAqd$LAXy1iA%h-CTTv*YR=jFx(R57s zfj0F3KX#xh!-KkuHCxRrXLTSZmhp_{8KAl{jl;>y@q1RA6X`Y2vzm;ky?w# za}R-An~#ZM-{g(4E{@E{y}lwZpCYEM^W?%+rm_5nP7Ve<6LkDRH8^BwRm3bfH;s_y z$yk?*lP9A|H*1~@AMGAau{pMbUxnv;P`Ky|-UWuIwwf)?d5fU>k4SzM$#1aug#r?x z_NJL$*=n}J0s$sYF{2oZcOeY(z!ftC?&qGsWKqj{cp>rXpodpwpNqG|9v_BT#xqsy zK79m+z2(|r4wxVQO#q+QCQfXp? z43$=!jBC?l+%5kYu^GfdIm5MvahiM3&al@3&gY_d^N4PmNzwt z6;b)p0?MehVx z3vrtG^9;NEln}KtX8Q}i_AjPDwe}7C^47kQ2x)46GwH1T{q*NRvyi@kNV|4Aw^V&b z$DLK4oo4MSOnIKHwZB>T7-%sZNE4K;XoiniMkj0=pQ@3d<?kzxXT?Hq`#LZB zj1h0fj4(rs`_O-esB&X*zdBu+s=DLM`v*pG2*7@g1y=XF*wXV zev@3+FUWQMW-6hHN@$Tw`$f65-zt~(+vL)IJC^o&)mYb0(MrGAuOPNEbYP|5T|;{j z)`%7NPCOmpzgT1M@?N@Y5x1@^0?BwcJu;(rzn)goDUc1;{9CcqZ=a!1=tRKb)eI1+ zxsrZ0xM;3q(!irxCC$92tG#E9hrZ!|p*Rxvetp_{&PRVMQ z!Cao~ayK#d_e6lR5auMY#!VB@zg3tMB4}{DtJh^1H;A03^?Fw8M3w=m`tR11p0rcF zrolvd0}{pa=#6=t>Q%2G%HRz#(MAS~SlCJ`(5s#WG+`(I7t>4|)Ix?$*|*fMx3a|> zHtRY0e^9|r3bsb3n4SDTtVt^uX+50Z#UGqBc8-WS?sS2a3e^c#C=iM_vgFyRR{iY}T zGe~|H$?qZgZ%DM6sUOK!d>dh0vOkLii&rrX8=CY944XfMLp|G3h8Z?)#E)H8+Y#Ab z$jrBp?IkFup`%v9{)1maSL&!ft7Z_gApEB0hSXSX{21N^78Nvk%x~>z#CBo-A(EA9 zn)qX0_B+ux*lIzBiHtHrU{^uG+f~roUZ}IH0Nrr7GC5tTmTT@1>NquBD_5o>q9OQqEs#N=86OWgE%dR*Z`~(ot|~VZDno|m<&sWuucfmgxGr^HXevw2V%>C*l*B9 zn++IkGZ1?W?x$@AmKWjnIkCGyY%|yh=Y3rC(M|(K`wDvLf;JK0g0>Ohf;JN1Vk>qU z;9?tdHq*s+=4_#h9n9&ci=Ei9fQy@$vyCopX3loH*u|V3ba4xFcGAVI%(;m!Zez~P zbg`Q`yXfL}=G;OTdzf=8UF;=@ZX<~9V9suOcqemir;EFovxhG3X3k!^xQ97+kTK^@ zGUnV%pWj8F56}?rqanVJhWLIO;sQp<~BKXcdTF(|X9@@W`M|y8^^p%_Kf3(K;Q zTtY@VtB#S5fLZ~jxfCDw#zSaOjEBnX3@@rY<+yY4N6{z-T9D1KWeB8!&WDqu4>tQR z8B4~9KoMmSOT%nWUXR$ahST_KBm`>QV+lMIKCvd8Eg7c$N*ai$@z+U!fU1Is)1gpu zkxeI#JXflmKaVkw9fl^O_ReJ?CmCl%!DPQI-^ET!#4vahWLwtWATs(25&|>s@zW8V zC5-ErNJ3vpqWKsg6InJbiF~dkNtu@O>Q#fgATI>SH-PSnpaQ34J|@)~nrbqV4+{rXV~Xn;O4ZnTel( za;on8mxX?0XL#_~DJv=Rfe`@YBU;Ji zqUlHKquy_^g}fiG?-UL^$eVcrrwZ$ZEF+INE4UcU(`x2vqde_({4j_Z$PeRhk$f4+ zUm^KNB!7?O?~wd6lK+e3{~)n|ZQsDv|3i|;Z5By7DKewAgku>dHweh%LCoLqsNQES zuRDiEfJToB!Sjr_AR2xyzpS^5Flz6hcrL#bXIbY%IRu(DMLd^Z7Cb~;&}{{@q|Go| zlhi`7p4tt5l?b+AwdEABWe;IX)UhY^{)XLO0E^S8`-?mdh45if+X|o7Ut|{eA}hXl zL4T25;ESC2;&=2H3vG7Hi!Yd^k9BGCV?>n}Zv&n>rin>!Yup-gyj(nP#&rb<953#uY4kj4~@eE=Y0sauP$I>i1R(b$*dp}`u1`E*wT0X^k zGlRTJsjIZQ$^`wJ2>ic6@()P9iUh^mxdbF4T7Q2Qkc;?V&E8LV?)+{h=SU~)`=sxk z;4Vk=-U;quH19pWC+0-K!;F{Nz?V7gSq0%!1W_%cu`vvt`1E>O^JB zn%M%9uaPZhX3N(tO}ltCE$rfzXNu9(#fE^}7fHY*_mZ?rGVZq_A@LSP8E#*i@oz)2 zoyCSG&=cPSG=Ymy+^1k-RDk1fj@IL3#@-pHFV^5(*M^JZSLhq{T;U&6pqKC^)Q1w8zpr~|xF1IkrKpF59~^1Uq%V_}OuBK}!W=({OYIx? zCrG}42KHucO{ga_guT9ILm8F?Twc|=o=(mXk@HNka`^MdEZ zK}}*$WQ*JrS(JMsTjid}Hn}IVUB)`@kQ*Y`$PJO5azkX7+z`1|#yVanV;y(PSjRmw z*7157>$q3?Fm8~6jyK9c$9>e=b=2BT(g|_1bVA%Boe=w_6XI4lA#z~hftVtH!<)zn z(IS__P2_L5nVb!`kf&ik+zj3A>|&MzCc|rQ8}bb}+mUg=*@2t`&Q4@SXk@tS?ee~G zZ(-$IS@|~MuBUD`nKzr&&2B15zY*nm_qUUW-X4n7yqD5D;Gu_^M)2To@PEs_xAP-N zD1_Y#a3K2}Z_-lsb{=I3=w`=w*?))zqo(Uw9prfU^+hd^(B7B0$Vn8%1(*#Tc(lB;f^;l~sRjIas|~-51cov~?MMrY#+0hLMvL z(9wB?BiiL**pDMCAcV>$;7C8Jn#bkA7g7b&En&yo*>$)%h?R?s7otMG6V&KCi&cD1 z9x}eOSR;c$qb~(T@!pM-gYtE)jds@oiqm!yTfp&Uvj^m=PTTLEh_O zCg(plisCLJFmba@d)GKtQ_10Dx^GVC(rnyufaV4}=bz<=tncJ=H_Dt?J`U z;N!f1_J=@ixk?zKb_@ z?zWICL{Ghd&&%ry5sYz@@yU2srEI4!d@*jC!ZDqjBVye6+$mb>wn+O)WFQ+FeiE%= zKM676pqC?2g{A`SB0<6Pb0v`*4BNx6hdIlPPBX!i)hk0{ho2x0g%P=UmgNN{*ml}{{+WDFs zSn->(cEO?`We82T5&s4IUn6-pl0RZHzA|C^ij7p%%3=I%SfHL}UJzx|#UK;mQXqeX zYeBwHEUqau6>{WSP*ko3KF0#TTS4H|WEJJ8?y1QySONEf?Vz0Jt|zXt@F7@qQ03=@ z!-4Q2SR;K1I;9Umm-Hc6D}4yoNgsl4YOM%l)FT5Kt(QIoz0!wZgY+TT2p<9tCZZUn z$n#)3xe*A@gFbRQ*hD@Do9S5OO>~0t7U49|FAhB3DtrgF3EzRu$CsDMH#et%#@8e}=$R{ZU*2?uJy z&%t!w-!RijyxCwoKd>aWLrqVDfW0Uc)c%*2$bFJ)JSKQ`uC4&$1IxyYJ}+TQkQvb? z0?v#);68L+>?jfXFym(s3_lvdn--$%o+MgMJ`W^Lel*02REMKI3whe8z!Oq9O#~cB z#FAD-Lw-z-#4@e;~QBt?$a3Aej3@FK2UnTgQb=I{`Z;TEWmrs zq-b?`wEyw7;Nj6~VF$@@2_#c}Ud#TzL#Y+F z!5&VWFwJOQXWshaHQx!-Y11h|Ei#$-Vj0hr5%zL7XG&(#5rv=pjwxRcgeJ>(}+6*86j8Nr4X%S?2^l zc?1ncO{%F48Rio{)cTO*w98c854C0|(Hmk5%j%)l%hIc;{!EU&9uKvClP0ZK)uGm7TJ8_C0|>{0><#;2XplVW z*AkC0RgsQEBF{Bu4phR2T045QRu8pCqRmk&`A}<|2D*Bv^>ue8YIp`+S){I9J=9uR zLB2U)1zA1R+QS&$8TU*3T5c(aTI)?8$II1P<-*L^WZg|4t9A$WdaygZ_tj>1c;Bnq z?(qC;%kJ>~uO_?0`(NL7hnH6E4u;*~gR6FjW$sl}|0AzYyTiv{rFMs(d0pBaety;N zfaKL?clgw*-C;Sp5;gqxs@);N?(n%)yF+B(+`>EKJ=bM-fQmarMJv0*IP?C293XyI zKWr(3!%hfR(Dcb?7S@0z+Z^HyG$9VF=%MzD!zvaEG=YmgI;>(N9Z->?C=(O{0-tZD zC=*-gn2LTnreZ5|H&eukt@uR6nAk4Pr`TbhPqEF*>QcA~gp%MN=$p;v%@*^HTtjReqk$($s8?F7Zt- zwX|saR}6U@kK`{k9N2p`rQ5#WiR$&VulX@*&E&s_pxGRbf1FQmz{DqjD9c?=CT*uA zC<$)u1}NCt%|Us^040)m>u%{zga`^uZnouZ|L29;K0o8xodf{jmx7st^?bcy){}6f z!F;~4B<8a_G3Jw%t+(>m!+v%r%YK0QU_S&&#(-o=3}|<<3<$u00TD2H1DM8GP$I;j z4;G--H!sxs`I*q|Nq~UvCovK{s5Rdx4@$PpU_ouF&5f|2+Y@6!8L#mj*TsQuPnH8g z00jpEAYIqXfNoEg0Ra{;Ai&bS5CzxeKZy_mCS$>W(D>fKKma|OWaB@^Smd(a+#TV- z9ep}*hcMk<^@r?GpJc`eW# zWn7O%l%288MlPt5^;8)WL_80YZiqNDeoROrl*e|U!>xo4^M&#-uN5U*5=lVx-YRU$ z@6~#rI&4KjwjD<^tCdbCDs08R$gmatb;4F$s}ryy6J20to&qzz z0-viN0mCdXJ5PaGUxA+$1zJ%HxUlLOcuP#~9n6QkdQ_7LZE+7qwa*(>5z=CF=x8mohjz=}B05lcpg zF+^hdQ-rBlANU(~U7)6YC{Kf{u#vhqYuV~ZxOU8<}l%%3VJOoA# zx5nD`T}m<{c?e$^i`jY%L8r8s9n%=@000=n%`}El$6WXpvI$ldvH{Ydl9n7ZWW!mH z+Z=i7#Nr|+P{;;vHX#~1pOS$TGFVCrL}oba5%QwT&n>3>X9DG?7hisUG39@b&piG^ zGpIlPkr`%OXJ?hp!JsLLR>aAs;XNf?8cK$DFQh4A4ew$;){s|daWV^E0(M6_9wh8@05#vM68Blhv+(d??Ri-(*3sC%ehw z$255@Ws}A61Xv`^DL*;iQXyn*LHdU6EHp?BY_d=TmGCBupVp(by2%2G-ef`Z6)9-_ zNVekJm9oj=liJF-vNl;@_R@ShdZUGw<R-tr7hhA2dgk2?BA1w$hG&r7`Qzt;U;i14cdU-G8PZc+pVSWe0V zOJS2{?1Ys;_~IU;ALM1ftYy1q6S#$t>cu1){6oC#f2@yG``JQi7B&E2+A|CQFCqDNNRVa4IH7c-OFOAceNHG`xmS94SXWDt?KGGY2IS!>JV*) zcaegvb#*n(8wxuz3PJ#P6v9y>3cLLyNLH$v{wVK}Kg)Y$B76;sX|AMaNIi$5-sD|1 zx9938>ga>8qF&DhVO3Lq?P}@|8gt#EHFa|BnVp#^#VvWzo_1dG0kj&GU5~;}`;oAg z3`p#g7E4A|PVLZ~rmZZp^N}}axi-#gZ^>J?5 z^?4gZbsEWVQfNst<_SGkN$c|(dlTsmhz_UG8;R)iP=JE^JV(iZDovhO%AYsHn^&7EPyB!7sq#;wOXF4L zk#KfrXz|4AKFlP3WmJzOro}^=T-D-DE&gqqtr%K71e%Z*uO@dxiyu}p5E!Eni5k)3 z#r^}1HY-+>KfyuT*Tk+zli!7Yr~&gu?R%!UG%!6=sZ|JI+HWy7Gc$IjhN;+@ot(T< zs$HCM$HcA-?-Hi6Qfd0i_(ga8k~>o>k&EzY6f<6#IO$fay!AGXAw(N{5DA?q@DU_G zgXE8qd>%=cOYcnYQ@uaZd$#xQdVjk26a4Sl-XG?_BfXFPfAo>7VgLXD literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/images/images_stub.py b/google_appengine/google/appengine/api/images/images_stub.py new file mode 100755 index 0000000..f266a37 --- /dev/null +++ b/google_appengine/google/appengine/api/images/images_stub.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the images API.""" + + + +import logging +import StringIO + +try: + import PIL + from PIL import _imaging + from PIL import Image +except ImportError: + import _imaging + import Image + +from google.appengine.api import apiproxy_stub +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import blobstore +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api import images +from google.appengine.api.images import images_service_pb +from google.appengine.runtime import apiproxy_errors + + +def _ArgbToRgbaTuple(argb): + """Convert from a single ARGB value to a tuple containing RGBA. + + Args: + argb: Signed 32 bit integer containing an ARGB value. + + Returns: + RGBA tuple. + """ + unsigned_argb = argb % 0x100000000 + return ((unsigned_argb >> 16) & 0xFF, + (unsigned_argb >> 8) & 0xFF, + unsigned_argb & 0xFF, + (unsigned_argb >> 24) & 0xFF) + + +def _BackendPremultiplication(color): + """Apply premultiplication and unpremultiplication to match production. + + Args: + color: color tuple as returned by _ArgbToRgbaTuple. + + Returns: + RGBA tuple. + """ + alpha = color[3] + rgb = color[0:3] + multiplied = [(x * (alpha + 1)) >> 8 for x in rgb] + if alpha: + alpha_inverse = 0xffffff / alpha + unmultiplied = [(x * alpha_inverse) >> 16 for x in multiplied] + else: + unmultiplied = [0] * 3 + + return tuple(unmultiplied + [alpha]) + + +class ImagesServiceStub(apiproxy_stub.APIProxyStub): + """Stub version of images API to be used with the dev_appserver.""" + + def __init__(self, service_name="images", host_prefix=""): + """Preloads PIL to load all modules in the unhardened environment. + + Args: + service_name: Service name expected for all calls. + host_prefix: the URL prefix (protocol://host:port) to preprend to + image urls on a call to GetUrlBase. + """ + super(ImagesServiceStub, self).__init__(service_name) + self._host_prefix = host_prefix + Image.init() + + def _Dynamic_Composite(self, request, response): + """Implementation of ImagesService::Composite. + + Based off documentation of the PIL library at + http://www.pythonware.com/library/pil/handbook/index.htm + + Args: + request: ImagesCompositeRequest, contains image request info. + response: ImagesCompositeResponse, contains transformed image. + """ + width = request.canvas().width() + height = request.canvas().height() + color = _ArgbToRgbaTuple(request.canvas().color()) + color = _BackendPremultiplication(color) + canvas = Image.new("RGBA", (width, height), color) + sources = [] + if (not request.canvas().width() or request.canvas().width() > 4000 or + not request.canvas().height() or request.canvas().height() > 4000): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + if not request.image_size(): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + if not request.options_size(): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + if request.options_size() > images.MAX_COMPOSITES_PER_REQUEST: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + for image in request.image_list(): + sources.append(self._OpenImageData(image)) + + for options in request.options_list(): + if (options.anchor() < images.TOP_LEFT or + options.anchor() > images.BOTTOM_RIGHT): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + if options.source_index() >= len(sources) or options.source_index() < 0: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + if options.opacity() < 0 or options.opacity() > 1: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + source = sources[options.source_index()] + x_anchor = (options.anchor() % 3) * 0.5 + y_anchor = (options.anchor() / 3) * 0.5 + x_offset = int(options.x_offset() + x_anchor * (width - source.size[0])) + y_offset = int(options.y_offset() + y_anchor * (height - source.size[1])) + alpha = options.opacity() * 255 + mask = Image.new("L", source.size, alpha) + canvas.paste(source, (x_offset, y_offset), mask) + response_value = self._EncodeImage(canvas, request.canvas().output()) + response.mutable_image().set_content(response_value) + + def _Dynamic_Histogram(self, request, response): + """Trivial implementation of ImagesService::Histogram. + + Based off documentation of the PIL library at + http://www.pythonware.com/library/pil/handbook/index.htm + + Args: + request: ImagesHistogramRequest, contains the image. + response: ImagesHistogramResponse, contains histogram of the image. + """ + image = self._OpenImageData(request.image()) + img_format = image.format + if img_format not in ("BMP", "GIF", "ICO", "JPEG", "PNG", "TIFF"): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.NOT_IMAGE) + image = image.convert("RGBA") + red = [0] * 256 + green = [0] * 256 + blue = [0] * 256 + for pixel in image.getdata(): + red[int((pixel[0] * pixel[3]) / 255)] += 1 + green[int((pixel[1] * pixel[3]) / 255)] += 1 + blue[int((pixel[2] * pixel[3]) / 255)] += 1 + histogram = response.mutable_histogram() + for value in red: + histogram.add_red(value) + for value in green: + histogram.add_green(value) + for value in blue: + histogram.add_blue(value) + + def _Dynamic_Transform(self, request, response): + """Trivial implementation of ImagesService::Transform. + + Based off documentation of the PIL library at + http://www.pythonware.com/library/pil/handbook/index.htm + + Args: + request: ImagesTransformRequest, contains image request info. + response: ImagesTransformResponse, contains transformed image. + """ + original_image = self._OpenImageData(request.image()) + + new_image = self._ProcessTransforms(original_image, + request.transform_list()) + + response_value = self._EncodeImage(new_image, request.output()) + response.mutable_image().set_content(response_value) + + def _Dynamic_GetUrlBase(self, request, response): + """Trivial implementation of ImagesService::GetUrlBase. + + Args: + request: ImagesGetUrlBaseRequest, contains a blobkey to an image + response: ImagesGetUrlBaseResponse, contains a url to serve the image + """ + response.set_url("%s/_ah/img/%s" % (self._host_prefix, request.blob_key())) + + def _EncodeImage(self, image, output_encoding): + """Encode the given image and return it in string form. + + Args: + image: PIL Image object, image to encode. + output_encoding: ImagesTransformRequest.OutputSettings object. + + Returns: + str with encoded image information in given encoding format. + """ + image_string = StringIO.StringIO() + + image_encoding = "PNG" + + if (output_encoding.mime_type() == images_service_pb.OutputSettings.JPEG): + image_encoding = "JPEG" + + image = image.convert("RGB") + + image.save(image_string, image_encoding) + + return image_string.getvalue() + + def _OpenImageData(self, image_data): + """Open image data from ImageData protocol buffer. + + Args: + image_data: ImageData protocol buffer containing image data or blob + reference. + + Returns: + Image containing the image data passed in or reference by blob-key. + + Raises: + ApplicationError if both content and blob-key are provided. + NOTE: 'content' must always be set because it is a required field, + however, it must be the empty string when a blob-key is provided. + """ + if image_data.content() and image_data.has_blob_key(): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.INVALID_BLOB_KEY) + + if image_data.has_blob_key(): + image = self._OpenBlob(image_data.blob_key()) + else: + image = self._OpenImage(image_data.content()) + + img_format = image.format + if img_format not in ("BMP", "GIF", "ICO", "JPEG", "PNG", "TIFF"): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.NOT_IMAGE) + return image + + def _OpenImage(self, image): + """Opens an image provided as a string. + + Args: + image: image data to be opened + + Raises: + apiproxy_errors.ApplicationError if the image cannot be opened or if it + is an unsupported format. + + Returns: + Image containing the image data passed in. + """ + if not image: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.NOT_IMAGE) + + image = StringIO.StringIO(image) + try: + return Image.open(image) + except IOError: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA) + + def _OpenBlob(self, blob_key): + key = datastore_types.Key.from_path(blobstore.BLOB_INFO_KIND, + blob_key, + namespace='') + try: + datastore.Get(key) + except datastore_errors.Error: + logging.exception('Blob with key %r does not exist', blob_key) + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.UNSPECIFIED_ERROR) + + blobstore_stub = apiproxy_stub_map.apiproxy.GetStub("blobstore") + + try: + blob_file = blobstore_stub.storage.OpenBlob(blob_key) + except IOError: + logging.exception('Could not get file for blob_key %r', blob_key) + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA) + + try: + return Image.open(blob_file) + except IOError: + logging.exception('Could not open image %r for blob_key %r', + blob_file, blob_key) + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_IMAGE_DATA) + + def _ValidateCropArg(self, arg): + """Check an argument for the Crop transform. + + Args: + arg: float, argument to Crop transform to check. + + Raises: + apiproxy_errors.ApplicationError on problem with argument. + """ + if not isinstance(arg, float): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + + if not (0 <= arg <= 1.0): + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + + def _CalculateNewDimensions(self, + current_width, + current_height, + req_width, + req_height): + """Get new resize dimensions keeping the current aspect ratio. + + This uses the more restricting of the two requested values to determine + the new ratio. + + Args: + current_width: int, current width of the image. + current_height: int, current height of the image. + req_width: int, requested new width of the image. + req_height: int, requested new height of the image. + + Returns: + tuple (width, height) which are both ints of the new ratio. + """ + + width_ratio = float(req_width) / current_width + height_ratio = float(req_height) / current_height + + if req_width == 0 or (width_ratio > height_ratio and req_height != 0): + return int(height_ratio * current_width), req_height + else: + return req_width, int(width_ratio * current_height) + + def _Resize(self, image, transform): + """Use PIL to resize the given image with the given transform. + + Args: + image: PIL.Image.Image object to resize. + transform: images_service_pb.Transform to use when resizing. + + Returns: + PIL.Image.Image with transforms performed on it. + + Raises: + BadRequestError if the resize data given is bad. + """ + width = 0 + height = 0 + + if transform.has_width(): + width = transform.width() + if width < 0 or 4000 < width: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + + if transform.has_height(): + height = transform.height() + if height < 0 or 4000 < height: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + + current_width, current_height = image.size + new_width, new_height = self._CalculateNewDimensions(current_width, + current_height, + width, + height) + + return image.resize((new_width, new_height), Image.ANTIALIAS) + + def _Rotate(self, image, transform): + """Use PIL to rotate the given image with the given transform. + + Args: + image: PIL.Image.Image object to rotate. + transform: images_service_pb.Transform to use when rotating. + + Returns: + PIL.Image.Image with transforms performed on it. + + Raises: + BadRequestError if the rotate data given is bad. + """ + degrees = transform.rotate() + if degrees < 0 or degrees % 90 != 0: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + degrees %= 360 + + degrees = 360 - degrees + return image.rotate(degrees) + + def _Crop(self, image, transform): + """Use PIL to crop the given image with the given transform. + + Args: + image: PIL.Image.Image object to crop. + transform: images_service_pb.Transform to use when cropping. + + Returns: + PIL.Image.Image with transforms performed on it. + + Raises: + BadRequestError if the crop data given is bad. + """ + left_x = 0.0 + top_y = 0.0 + right_x = 1.0 + bottom_y = 1.0 + + if transform.has_crop_left_x(): + left_x = transform.crop_left_x() + self._ValidateCropArg(left_x) + + if transform.has_crop_top_y(): + top_y = transform.crop_top_y() + self._ValidateCropArg(top_y) + + if transform.has_crop_right_x(): + right_x = transform.crop_right_x() + self._ValidateCropArg(right_x) + + if transform.has_crop_bottom_y(): + bottom_y = transform.crop_bottom_y() + self._ValidateCropArg(bottom_y) + + width, height = image.size + + box = (int(transform.crop_left_x() * width), + int(transform.crop_top_y() * height), + int(transform.crop_right_x() * width), + int(transform.crop_bottom_y() * height)) + + return image.crop(box) + + def _ProcessTransforms(self, image, transforms): + """Execute PIL operations based on transform values. + + Args: + image: PIL.Image.Image instance, image to manipulate. + trasnforms: list of ImagesTransformRequest.Transform objects. + + Returns: + PIL.Image.Image with transforms performed on it. + + Raises: + BadRequestError if we are passed more than one of the same type of + transform. + """ + new_image = image + if len(transforms) > images.MAX_TRANSFORMS_PER_REQUEST: + raise apiproxy_errors.ApplicationError( + images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) + for transform in transforms: + if transform.has_width() or transform.has_height(): + new_image = self._Resize(new_image, transform) + + elif transform.has_rotate(): + new_image = self._Rotate(new_image, transform) + + elif transform.has_horizontal_flip(): + new_image = new_image.transpose(Image.FLIP_LEFT_RIGHT) + + elif transform.has_vertical_flip(): + new_image = new_image.transpose(Image.FLIP_TOP_BOTTOM) + + elif (transform.has_crop_left_x() or + transform.has_crop_top_y() or + transform.has_crop_right_x() or + transform.has_crop_bottom_y()): + new_image = self._Crop(new_image, transform) + + elif transform.has_autolevels(): + logging.info("I'm Feeling Lucky autolevels will be visible once this " + "application is deployed.") + else: + logging.warn("Found no transformations found to perform.") + + return new_image diff --git a/google_appengine/google/appengine/api/images/images_stub.pyc b/google_appengine/google/appengine/api/images/images_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..110ceb992b5af29ba792637ecc4bb71ef5f188ab GIT binary patch literal 16704 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

du%@QKF1THdOELecm zH$?b0!7K`#8}TWfj4iqk+x4~C+bLlqD+NGJs>~g}Nq4-$>fkT2!rcslj zFH2;hhHQ)Hv;z^v8Gsrp8)b^Q2Mt8u*KT@kPW12)-0?k;QqY zxFE&jH4UA(5>DrC7dv`s&Pb9(xOvU&)NpYm99=F4Gp&DYBpc_?yX`_LC%cssle`Cj;V+z~xjJd3s+!2J#?-=YkBS zAVnnNV2G4UdnDiPFIh!^K0l-i<}^1jGWI)PPV$BA!`>L}J>{uWXXkM(z-~?f71Y zLztZIiTH#!nl*7XB)j2o<}5kWM54x`OI1Dxt{k07I-autantb$iJL^H5q^Dxb7g8c zty*Qi5stE!@3hS&*KHwru+pvX9hpuy4qEQM+lq+VRtrZ9`(EU2Q*a#M1TaPh!pU+@ z(0UEG(+ZB<#&iUiVJYbPNW^9X>V4ed7x)E_OnZ6?KU=Zz8`9p((dotNp3Bh}`Y6_4 zqWCKm_&sUr-fPzer^tMDf?fU~^gh7>N6X%6d7aFE?*x(d_o1SfR+i=Nw7xK%Xi?n8 zqlxRzDI{)M%LQxe??A@SQT#oMe?ak1DyPu11-5>yRPxKBoq!(>oX|ggqF+l$WS+5H zrFHQo=~8Q(kpEknt{pDxE5x`ichi_4<(dD;AD+gFV+Fp@t$$vHBLLf$!%16yYDQJ| z>+D8Qw{89rll2}72gMEw_^on9i|=;fsD|^$Y$Q2e<2~n1=5=Orv!%I&^}&Gx4ud0; z3K*D?!RoiFFIBHptJUk(7ppg_x2t3Hd8Im0{n`|=Cl=Dky!(7cCKpkcS8!0P56s{a zEVprFCkG7pfxnP6osvXPp2hhl^yhO6KBVBo2|nQvsk(JR-)WhLTs*mP9#GizW3R1) z1O6Qaz}Ux_TVGyB78-{WyaJcG%knvjB+{28Lh_kcgiKNDhrC_5l+58Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg|0JrhZOhQn7w~d}dx|NqoFsL1hUC T&_J8q{FKt1R6CHh#UN_{2T~{q literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/labs/taskqueue/__init__.py b/google_appengine/google/appengine/api/labs/taskqueue/__init__.py new file mode 100644 index 0000000..cf9ea5a --- /dev/null +++ b/google_appengine/google/appengine/api/labs/taskqueue/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Task Queue API module.""" + +from taskqueue import * diff --git a/google_appengine/google/appengine/api/labs/taskqueue/__init__.pyc b/google_appengine/google/appengine/api/labs/taskqueue/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2632734dd92496a7141e6c70c81c8f22cc354aae GIT binary patch literal 260 zcwR-0O$x#=5QWn!{(&12S1!6NE}9F7D7X0D41KbWR#{ZyG|+|WepBon}j pLa0P4Q5&=|SRrY~u7>{8DS%Yc0z}2j_Aq+>dvh^zUGB4v{{_x1LsS3& literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.py b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.py new file mode 100755 index 0000000..bb9534d --- /dev/null +++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.py @@ -0,0 +1,805 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Task Queue API. + +Enables an application to queue background work for itself. Work is done through +webhooks that process tasks pushed from a queue. Tasks will execute in +best-effort order of ETA. Webhooks that fail will cause tasks to be retried at a +later time. Multiple queues may exist with independent throttling controls. + +Webhook URLs may be specified directly for Tasks, or the default URL scheme +may be used, which will translate Task names into URLs relative to a Queue's +base path. A default queue is also provided for simple usage. +""" + + + +import calendar +import datetime +import os +import re +import time +import urllib +import urlparse + +import taskqueue_service_pb + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import namespace_manager +from google.appengine.api import urlfetch +from google.appengine.runtime import apiproxy_errors + + +class Error(Exception): + """Base-class for exceptions in this module.""" + + +class UnknownQueueError(Error): + """The queue specified is unknown.""" + + +class TransientError(Error): + """There was a transient error while accessing the queue. + + Please Try again later. + """ + + +class InternalError(Error): + """There was an internal error while accessing this queue. + + If this problem continues, please contact the App Engine team through + our support forum with a description of your problem. + """ + + +class InvalidTaskError(Error): + """The task's parameters, headers, or method is invalid.""" + + +class InvalidTaskNameError(InvalidTaskError): + """The task's name is invalid.""" + + +class TaskTooLargeError(InvalidTaskError): + """The task is too large with its headers and payload.""" + + +class TaskAlreadyExistsError(InvalidTaskError): + """Task already exists. It has not yet run.""" + + +class TombstonedTaskError(InvalidTaskError): + """Task has been tombstoned.""" + + +class InvalidUrlError(InvalidTaskError): + """The task's relative URL is invalid.""" + + +class BadTaskStateError(Error): + """The task is in the wrong state for the requested operation.""" + + +class InvalidQueueError(Error): + """The Queue's configuration is invalid.""" + + +class InvalidQueueNameError(InvalidQueueError): + """The Queue's name is invalid.""" + + +class _RelativeUrlError(Error): + """The relative URL supplied is invalid.""" + + +class PermissionDeniedError(Error): + """The requested operation is not allowed for this app.""" + + +class DuplicateTaskNameError(Error): + """The add arguments contain tasks with identical names.""" + + +class TooManyTasksError(Error): + """Too many tasks were present in a single function call.""" + + +class DatastoreError(Error): + """There was a datastore error while accessing the queue.""" + + +class BadTransactionStateError(Error): + """The state of the current transaction does not permit this operation.""" + +BadTransactionState = BadTransactionStateError + +MAX_QUEUE_NAME_LENGTH = 100 + +MAX_TASK_NAME_LENGTH = 500 + +MAX_TASK_SIZE_BYTES = 10 * (2 ** 10) + +MAX_URL_LENGTH = 2083 + +_DEFAULT_QUEUE = 'default' + +_DEFAULT_QUEUE_PATH = '/_ah/queue' + +_METHOD_MAP = { + 'GET': taskqueue_service_pb.TaskQueueAddRequest.GET, + 'POST': taskqueue_service_pb.TaskQueueAddRequest.POST, + 'HEAD': taskqueue_service_pb.TaskQueueAddRequest.HEAD, + 'PUT': taskqueue_service_pb.TaskQueueAddRequest.PUT, + 'DELETE': taskqueue_service_pb.TaskQueueAddRequest.DELETE, +} + +_NON_POST_METHODS = frozenset(['GET', 'HEAD', 'PUT', 'DELETE']) + +_BODY_METHODS = frozenset(['POST', 'PUT']) + +_TASK_NAME_PATTERN = r'^[a-zA-Z0-9-]{1,%s}$' % MAX_TASK_NAME_LENGTH + +_TASK_NAME_RE = re.compile(_TASK_NAME_PATTERN) + +_QUEUE_NAME_PATTERN = r'^[a-zA-Z0-9-]{1,%s}$' % MAX_QUEUE_NAME_LENGTH + +_QUEUE_NAME_RE = re.compile(_QUEUE_NAME_PATTERN) + +_ERROR_MAPPING = { + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE: UnknownQueueError, + taskqueue_service_pb.TaskQueueServiceError.TRANSIENT_ERROR: + TransientError, + taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR: InternalError, + taskqueue_service_pb.TaskQueueServiceError.TASK_TOO_LARGE: + TaskTooLargeError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_TASK_NAME: + InvalidTaskNameError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_NAME: + InvalidQueueNameError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_URL: InvalidUrlError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_RATE: + InvalidQueueError, + taskqueue_service_pb.TaskQueueServiceError.PERMISSION_DENIED: + PermissionDeniedError, + taskqueue_service_pb.TaskQueueServiceError.TASK_ALREADY_EXISTS: + TaskAlreadyExistsError, + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_TASK: + TombstonedTaskError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_ETA: InvalidTaskError, + taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST: Error, + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_TASK: Error, + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_QUEUE: Error, + taskqueue_service_pb.TaskQueueServiceError.DUPLICATE_TASK_NAME: + DuplicateTaskNameError, + + taskqueue_service_pb.TaskQueueServiceError.TOO_MANY_TASKS: + TooManyTasksError, + +} + +_PRESERVE_ENVIRONMENT_HEADERS = ( + ('X-AppEngine-Default-Namespace', 'HTTP_X_APPENGINE_DEFAULT_NAMESPACE'),) + + +class _UTCTimeZone(datetime.tzinfo): + """UTC timezone.""" + + ZERO = datetime.timedelta(0) + + def utcoffset(self, dt): + return self.ZERO + + def dst(self, dt): + return self.ZERO + + def tzname(self, dt): + return 'UTC' + + +_UTC = _UTCTimeZone() + + +def _parse_relative_url(relative_url): + """Parses a relative URL and splits it into its path and query string. + + Args: + relative_url: The relative URL, starting with a '/'. + + Returns: + Tuple (path, query) where: + path: The path in the relative URL. + query: The query string in the URL without the '?' character. + + Raises: + _RelativeUrlError if the relative_url is invalid for whatever reason + """ + if not relative_url: + raise _RelativeUrlError('Relative URL is empty') + (scheme, netloc, path, query, fragment) = urlparse.urlsplit(relative_url) + if scheme or netloc: + raise _RelativeUrlError('Relative URL may not have a scheme or location') + if fragment: + raise _RelativeUrlError('Relative URL may not specify a fragment') + if not path or path[0] != '/': + raise _RelativeUrlError('Relative URL path must start with "/"') + return path, query + + +def _flatten_params(params): + """Converts a dictionary of parameters to a list of parameters. + + Any unicode strings in keys or values will be encoded as UTF-8. + + Args: + params: Dictionary mapping parameter keys to values. Values will be + converted to a string and added to the list as tuple (key, value). If + a values is iterable and not a string, each contained value will be + added as a separate (key, value) tuple. + + Returns: + List of (key, value) tuples. + """ + def get_string(value): + if isinstance(value, unicode): + return unicode(value).encode('utf-8') + else: + return str(value) + + param_list = [] + for key, value in params.iteritems(): + key = get_string(key) + if isinstance(value, basestring): + param_list.append((key, get_string(value))) + else: + try: + iterator = iter(value) + except TypeError: + param_list.append((key, str(value))) + else: + param_list.extend((key, get_string(v)) for v in iterator) + + return param_list + + +class Task(object): + """Represents a single Task on a queue.""" + + __CONSTRUCTOR_KWARGS = frozenset([ + 'countdown', 'eta', 'headers', 'method', 'name', 'params', 'url']) + + __eta_posix = None + + def __init__(self, payload=None, **kwargs): + """Initializer. + + All parameters are optional. + + Args: + payload: The payload data for this Task that will be delivered to the + webhook as the HTTP request body. This is only allowed for POST and PUT + methods. + countdown: Time in seconds into the future that this Task should execute. + Defaults to zero. + eta: Absolute time when the Task should execute. May not be specified + if 'countdown' is also supplied. This may be timezone-aware or + timezone-naive. + headers: Dictionary of headers to pass to the webhook. Values in the + dictionary may be iterable to indicate repeated header fields. + method: Method to use when accessing the webhook. Defaults to 'POST'. + name: Name to give the Task; if not specified, a name will be + auto-generated when added to a queue and assigned to this object. Must + match the _TASK_NAME_PATTERN regular expression. + params: Dictionary of parameters to use for this Task. For POST requests + these params will be encoded as 'application/x-www-form-urlencoded' and + set to the payload. For all other methods, the parameters will be + converted to a query string. May not be specified if the URL already + contains a query string. + url: Relative URL where the webhook that should handle this task is + located for this application. May have a query string unless this is + a POST method. + + Raises: + InvalidTaskError if any of the parameters are invalid; + InvalidTaskNameError if the task name is invalid; InvalidUrlError if + the task URL is invalid or too long; TaskTooLargeError if the task with + its payload is too large. + """ + args_diff = set(kwargs.iterkeys()) - self.__CONSTRUCTOR_KWARGS + if args_diff: + raise TypeError('Invalid arguments: %s' % ', '.join(args_diff)) + + self.__name = kwargs.get('name') + if self.__name and not _TASK_NAME_RE.match(self.__name): + raise InvalidTaskNameError( + 'Task name does not match expression "%s"; found %s' % + (_TASK_NAME_PATTERN, self.__name)) + + self.__default_url, self.__relative_url, query = Task.__determine_url( + kwargs.get('url', '')) + self.__headers = urlfetch._CaselessDict() + self.__headers.update(kwargs.get('headers', {})) + self.__method = kwargs.get('method', 'POST').upper() + self.__payload = None + params = kwargs.get('params', {}) + + for header_name, environ_name in _PRESERVE_ENVIRONMENT_HEADERS: + value = os.environ.get(environ_name) + if value is not None: + self.__headers.setdefault(header_name, value) + + self.__headers.setdefault('X-AppEngine-Current-Namespace', + namespace_manager.get_namespace()) + if query and params: + raise InvalidTaskError('Query string and parameters both present; ' + 'only one of these may be supplied') + + if self.__method == 'POST': + if payload and params: + raise InvalidTaskError('Message body and parameters both present for ' + 'POST method; only one of these may be supplied') + elif query: + raise InvalidTaskError('POST method may not have a query string; ' + 'use the "params" keyword argument instead') + elif params: + self.__payload = Task.__encode_params(params) + self.__headers.setdefault( + 'content-type', 'application/x-www-form-urlencoded') + elif payload is not None: + self.__payload = Task.__convert_payload(payload, self.__headers) + elif self.__method in _NON_POST_METHODS: + if payload and self.__method not in _BODY_METHODS: + raise InvalidTaskError('Payload may only be specified for methods %s' % + ', '.join(_BODY_METHODS)) + if payload: + self.__payload = Task.__convert_payload(payload, self.__headers) + if params: + query = Task.__encode_params(params) + if query: + self.__relative_url = '%s?%s' % (self.__relative_url, query) + else: + raise InvalidTaskError('Invalid method: %s' % self.__method) + + self.__headers_list = _flatten_params(self.__headers) + self.__eta_posix = Task.__determine_eta_posix( + kwargs.get('eta'), kwargs.get('countdown')) + self.__eta = None + self.__enqueued = False + + if self.size > MAX_TASK_SIZE_BYTES: + raise TaskTooLargeError('Task size must be less than %d; found %d' % + (MAX_TASK_SIZE_BYTES, self.size)) + + @staticmethod + def __determine_url(relative_url): + """Determines the URL of a task given a relative URL and a name. + + Args: + relative_url: The relative URL for the Task. + + Returns: + Tuple (default_url, relative_url, query) where: + default_url: True if this Task is using the default URL scheme; + False otherwise. + relative_url: String containing the relative URL for this Task. + query: The query string for this task. + + Raises: + InvalidUrlError if the relative_url is invalid. + """ + if not relative_url: + default_url, query = True, '' + else: + default_url = False + try: + relative_url, query = _parse_relative_url(relative_url) + except _RelativeUrlError, e: + raise InvalidUrlError(e) + + if len(relative_url) > MAX_URL_LENGTH: + raise InvalidUrlError( + 'Task URL must be less than %d characters; found %d' % + (MAX_URL_LENGTH, len(relative_url))) + + return (default_url, relative_url, query) + + @staticmethod + def __determine_eta_posix(eta=None, countdown=None, current_time=time.time): + """Determines the ETA for a task. + + If 'eta' and 'countdown' are both None, the current time will be used. + Otherwise, only one of them may be specified. + + Args: + eta: A datetime.datetime specifying the absolute ETA or None; + this may be timezone-aware or timezone-naive. + countdown: Count in seconds into the future from the present time that + the ETA should be assigned to. + + Returns: + A float giving a POSIX timestamp containing the ETA. + + Raises: + InvalidTaskError if the parameters are invalid. + """ + if eta is not None and countdown is not None: + raise InvalidTaskError('May not use a countdown and ETA together') + elif eta is not None: + if not isinstance(eta, datetime.datetime): + raise InvalidTaskError('ETA must be a datetime.datetime instance') + elif eta.tzinfo is None: + return time.mktime(eta.timetuple()) + eta.microsecond*1e-6 + else: + return calendar.timegm(eta.utctimetuple()) + eta.microsecond*1e-6 + elif countdown is not None: + try: + countdown = float(countdown) + except ValueError: + raise InvalidTaskError('Countdown must be a number') + except OverflowError: + raise InvalidTaskError('Countdown out of range') + else: + return current_time() + countdown + else: + return current_time() + + @staticmethod + def __encode_params(params): + """URL-encodes a list of parameters. + + Args: + params: Dictionary of parameters, possibly with iterable values. + + Returns: + URL-encoded version of the params, ready to be added to a query string or + POST body. + """ + return urllib.urlencode(_flatten_params(params)) + + @staticmethod + def __convert_payload(payload, headers): + """Converts a Task payload into UTF-8 and sets headers if necessary. + + Args: + payload: The payload data to convert. + headers: Dictionary of headers. + + Returns: + The payload as a non-unicode string. + + Raises: + InvalidTaskError if the payload is not a string or unicode instance. + """ + if isinstance(payload, unicode): + headers.setdefault('content-type', 'text/plain; charset=utf-8') + payload = payload.encode('utf-8') + elif not isinstance(payload, str): + raise InvalidTaskError( + 'Task payloads must be strings; invalid payload: %r' % payload) + return payload + + @property + def on_queue_url(self): + """Returns True if this Task will run on the queue's URL.""" + return self.__default_url + + @property + def eta_posix(self): + """Returns a POSIX timestamp giving when this Task will execute.""" + if self.__eta_posix is None and self.__eta is not None: + self.__eta_posix = Task.__determine_eta_posix(self.__eta) + return self.__eta_posix + + @property + def eta(self): + """Returns a datetime when this Task will execute.""" + if self.__eta is None and self.__eta_posix is not None: + self.__eta = datetime.datetime.fromtimestamp(self.__eta_posix, _UTC) + return self.__eta + + @property + def headers(self): + """Returns a copy of the headers for this Task.""" + return self.__headers.copy() + + @property + def method(self): + """Returns the method to use for this Task.""" + return self.__method + + @property + def name(self): + """Returns the name of this Task. + + Will be None if using auto-assigned Task names and this Task has not yet + been added to a Queue. + """ + return self.__name + + @property + def payload(self): + """Returns the payload for this task, which may be None.""" + return self.__payload + + @property + def size(self): + """Returns the size of this task in bytes.""" + HEADER_SEPERATOR = len(': \r\n') + header_size = sum((len(key) + len(value) + HEADER_SEPERATOR) + for key, value in self.__headers_list) + return (len(self.__method) + len(self.__payload or '') + + len(self.__relative_url) + header_size) + + @property + def url(self): + """Returns the relative URL for this Task.""" + return self.__relative_url + + @property + def was_enqueued(self): + """Returns True if this Task has been enqueued. + + Note: This will not check if this task already exists in the queue. + """ + return self.__enqueued + + def add(self, queue_name=_DEFAULT_QUEUE, transactional=False): + """Adds this Task to a queue. See Queue.add.""" + return Queue(queue_name).add(self, transactional=transactional) + + +class Queue(object): + """Represents a Queue.""" + + def __init__(self, name=_DEFAULT_QUEUE): + """Initializer. + + Args: + name: Name of this queue. If not supplied, defaults to the default queue. + + Raises: + InvalidQueueNameError if the queue name is invalid. + """ + if not _QUEUE_NAME_RE.match(name): + raise InvalidQueueNameError( + 'Queue name does not match pattern "%s"; found %s' % + (_QUEUE_NAME_PATTERN, name)) + self.__name = name + self.__url = '%s/%s' % (_DEFAULT_QUEUE_PATH, self.__name) + + def add(self, task, transactional=False): + """Adds a Task or list of Tasks to this Queue. + + If a list of more than one Tasks is given, a raised exception does not + guarantee that no tasks were added to the queue (unless transactional is set + to True). To determine which tasks were successfully added when an exception + is raised, check the Task.was_enqueued property. + + Args: + task: A Task instance or a list of Task instances that will added to the + queue. + transactional: If False adds the Task(s) to a queue irrespectively to the + enclosing transaction success or failure. An exception is raised if True + and called outside of a transaction. (optional) + + Returns: + The Task or list of tasks that was supplied to this method. + + Raises: + BadTaskStateError: if the Task(s) has already been added to a queue. + BadTransactionStateError: if the transactional argument is true but this + call is being made outside of the context of a transaction. + Error-subclass on application errors. + """ + try: + tasks = list(iter(task)) + except TypeError: + tasks = [task] + multiple = False + else: + multiple = True + + self.__AddTasks(tasks, transactional) + + if multiple: + return tasks + else: + assert len(tasks) == 1 + return tasks[0] + + def __AddTasks(self, tasks, transactional): + """Internal implementation of .add() where tasks must be a list.""" + + request = taskqueue_service_pb.TaskQueueBulkAddRequest() + response = taskqueue_service_pb.TaskQueueBulkAddResponse() + + task_names = set() + for task in tasks: + if task.name: + if task.name in task_names: + raise DuplicateTaskNameError( + 'The task name %r is used more than once in the request' % + task.name) + task_names.add(task.name) + + self.__FillAddRequest(task, request.add_add_request(), transactional) + + try: + apiproxy_stub_map.MakeSyncCall('taskqueue', 'BulkAdd', request, response) + except apiproxy_errors.ApplicationError, e: + raise self.__TranslateError(e.application_error, e.error_detail) + + assert response.taskresult_size() == len(tasks), ( + 'expected %d results from BulkAdd(), got %d' % ( + len(tasks), response.taskresult_size())) + + exception = None + for task, task_result in zip(tasks, response.taskresult_list()): + if task_result.result() == taskqueue_service_pb.TaskQueueServiceError.OK: + if task_result.has_chosen_task_name(): + task._Task__name = task_result.chosen_task_name() + task._Task__enqueued = True + elif (task_result.result() == + taskqueue_service_pb.TaskQueueServiceError.SKIPPED): + pass + elif exception is None: + exception = self.__TranslateError(task_result.result()) + + if exception is not None: + raise exception + + return tasks + + def __FillAddRequest(self, task, task_request, transactional): + """Populates a TaskQueueAddRequest with the data from a Task instance. + + Args: + task: The Task instance to use as a source for the data to be added to + task_request. + task_request: The taskqueue_service_pb.TaskQueueAddRequest to populate. + transactional: If true then populates the task_request.transaction message + with information from the enclosing transaction (if any). + + Raises: + BadTaskStateError: If the task was already added to a Queue. + BadTransactionStateError: If the transactional argument is True and there + is no enclosing transaction. + InvalidTaskNameError: If the transactional argument is True and the task + is named. + """ + if task.was_enqueued: + raise BadTaskStateError('Task has already been enqueued') + + adjusted_url = task.url + if task.on_queue_url: + adjusted_url = self.__url + task.url + + + task_request.set_queue_name(self.__name) + task_request.set_eta_usec(long(task.eta_posix * 1e6)) + task_request.set_method(_METHOD_MAP.get(task.method)) + task_request.set_url(adjusted_url) + + if task.name: + task_request.set_task_name(task.name) + else: + task_request.set_task_name('') + + if task.payload: + task_request.set_body(task.payload) + for key, value in _flatten_params(task.headers): + header = task_request.add_header() + header.set_key(key) + header.set_value(value) + + if transactional: + from google.appengine.api import datastore + if not datastore._MaybeSetupTransaction(task_request, []): + raise BadTransactionStateError( + 'Transactional adds are not allowed outside of transactions') + + if task_request.has_transaction() and task.name: + raise InvalidTaskNameError( + 'Task bound to a transaction cannot be named.') + + @property + def name(self): + """Returns the name of this queue.""" + return self.__name + + @staticmethod + def __TranslateError(error, detail=''): + """Translates a TaskQueueServiceError into an exception. + + Args: + error: Value from TaskQueueServiceError enum. + detail: A human-readable description of the error. + + Returns: + The corresponding Exception sub-class for that error code. + """ + if (error >= taskqueue_service_pb.TaskQueueServiceError.DATASTORE_ERROR + and isinstance(error, int)): + from google.appengine.api import datastore + datastore_exception = datastore._DatastoreExceptionFromErrorCodeAndDetail( + error - taskqueue_service_pb.TaskQueueServiceError.DATASTORE_ERROR, + detail) + + class JointException(datastore_exception.__class__, DatastoreError): + """There was a datastore error while accessing the queue.""" + __msg = (u'taskqueue.DatastoreError caused by: %s %s' % + (datastore_exception.__class__, detail)) + def __str__(self): + return JointException.__msg + + return JointException() + else: + exception_class = _ERROR_MAPPING.get(error, None) + if exception_class: + return exception_class(detail) + else: + return Error('Application error %s: %s' % (error, detail)) + + +def add(*args, **kwargs): + """Convenience method will create a Task and add it to a queue. + + All parameters are optional. + + Args: + name: Name to give the Task; if not specified, a name will be + auto-generated when added to a queue and assigned to this object. Must + match the _TASK_NAME_PATTERN regular expression. + queue_name: Name of this queue. If not supplied, defaults to + the default queue. + url: Relative URL where the webhook that should handle this task is + located for this application. May have a query string unless this is + a POST method. + method: Method to use when accessing the webhook. Defaults to 'POST'. + headers: Dictionary of headers to pass to the webhook. Values in the + dictionary may be iterable to indicate repeated header fields. + payload: The payload data for this Task that will be delivered to the + webhook as the HTTP request body. This is only allowed for POST and PUT + methods. + params: Dictionary of parameters to use for this Task. For POST requests + these params will be encoded as 'application/x-www-form-urlencoded' and + set to the payload. For all other methods, the parameters will be + converted to a query string. May not be specified if the URL already + contains a query string. + transactional: If False adds the Task(s) to a queue irrespectively to the + enclosing transaction success or failure. An exception is raised if True + and called outside of a transaction. (optional) + countdown: Time in seconds into the future that this Task should execute. + Defaults to zero. + eta: Absolute time when the Task should execute. May not be specified + if 'countdown' is also supplied. This may be timezone-aware or + timezone-naive. + + Returns: + The Task that was added to the queue. + + Raises: + InvalidTaskError if any of the parameters are invalid; + InvalidTaskNameError if the task name is invalid; InvalidUrlError if + the task URL is invalid or too long; TaskTooLargeError if the task with + its payload is too large. + """ + transactional = kwargs.pop('transactional', False) + queue_name = kwargs.pop('queue_name', _DEFAULT_QUEUE) + return Task(*args, **kwargs).add( + queue_name=queue_name, transactional=transactional) diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.pyc b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53c97f6e67af7f490b8f718758bbc3d27f738f93 GIT binary patch literal 33400 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 O2G36ZsX9IRum2xp`4cbz literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py new file mode 100755 index 0000000..077b26e --- /dev/null +++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.py @@ -0,0 +1,4588 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.datastore.datastore_v3_pb import * +import google.appengine.datastore.datastore_v3_pb +from google.net.proto.message_set import MessageSet +class TaskQueueServiceError(ProtocolBuffer.ProtocolMessage): + + OK = 0 + UNKNOWN_QUEUE = 1 + TRANSIENT_ERROR = 2 + INTERNAL_ERROR = 3 + TASK_TOO_LARGE = 4 + INVALID_TASK_NAME = 5 + INVALID_QUEUE_NAME = 6 + INVALID_URL = 7 + INVALID_QUEUE_RATE = 8 + PERMISSION_DENIED = 9 + TASK_ALREADY_EXISTS = 10 + TOMBSTONED_TASK = 11 + INVALID_ETA = 12 + INVALID_REQUEST = 13 + UNKNOWN_TASK = 14 + TOMBSTONED_QUEUE = 15 + DUPLICATE_TASK_NAME = 16 + SKIPPED = 17 + TOO_MANY_TASKS = 18 + INVALID_PAYLOAD = 19 + DATASTORE_ERROR = 10000 + + _ErrorCode_NAMES = { + 0: "OK", + 1: "UNKNOWN_QUEUE", + 2: "TRANSIENT_ERROR", + 3: "INTERNAL_ERROR", + 4: "TASK_TOO_LARGE", + 5: "INVALID_TASK_NAME", + 6: "INVALID_QUEUE_NAME", + 7: "INVALID_URL", + 8: "INVALID_QUEUE_RATE", + 9: "PERMISSION_DENIED", + 10: "TASK_ALREADY_EXISTS", + 11: "TOMBSTONED_TASK", + 12: "INVALID_ETA", + 13: "INVALID_REQUEST", + 14: "UNKNOWN_TASK", + 15: "TOMBSTONED_QUEUE", + 16: "DUPLICATE_TASK_NAME", + 17: "SKIPPED", + 18: "TOO_MANY_TASKS", + 19: "INVALID_PAYLOAD", + 10000: "DATASTORE_ERROR", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueAddRequest_Header(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(58) + out.putPrefixedString(self.key_) + out.putVarInt32(66) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 52: break + if tt == 58: + self.set_key(d.getPrefixedString()) + continue + if tt == 66: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + return res + +class TaskQueueAddRequest_CronTimetable(ProtocolBuffer.ProtocolMessage): + has_schedule_ = 0 + schedule_ = "" + has_timezone_ = 0 + timezone_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def schedule(self): return self.schedule_ + + def set_schedule(self, x): + self.has_schedule_ = 1 + self.schedule_ = x + + def clear_schedule(self): + if self.has_schedule_: + self.has_schedule_ = 0 + self.schedule_ = "" + + def has_schedule(self): return self.has_schedule_ + + def timezone(self): return self.timezone_ + + def set_timezone(self, x): + self.has_timezone_ = 1 + self.timezone_ = x + + def clear_timezone(self): + if self.has_timezone_: + self.has_timezone_ = 0 + self.timezone_ = "" + + def has_timezone(self): return self.has_timezone_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_schedule()): self.set_schedule(x.schedule()) + if (x.has_timezone()): self.set_timezone(x.timezone()) + + def Equals(self, x): + if x is self: return 1 + if self.has_schedule_ != x.has_schedule_: return 0 + if self.has_schedule_ and self.schedule_ != x.schedule_: return 0 + if self.has_timezone_ != x.has_timezone_: return 0 + if self.has_timezone_ and self.timezone_ != x.timezone_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_schedule_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: schedule not set.') + if (not self.has_timezone_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: timezone not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.schedule_)) + n += self.lengthString(len(self.timezone_)) + return n + 2 + + def Clear(self): + self.clear_schedule() + self.clear_timezone() + + def OutputUnchecked(self, out): + out.putVarInt32(106) + out.putPrefixedString(self.schedule_) + out.putVarInt32(114) + out.putPrefixedString(self.timezone_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 100: break + if tt == 106: + self.set_schedule(d.getPrefixedString()) + continue + if tt == 114: + self.set_timezone(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_schedule_: res+=prefix+("schedule: %s\n" % self.DebugFormatString(self.schedule_)) + if self.has_timezone_: res+=prefix+("timezone: %s\n" % self.DebugFormatString(self.timezone_)) + return res + +class TaskQueueAddRequest(ProtocolBuffer.ProtocolMessage): + + GET = 1 + POST = 2 + HEAD = 3 + PUT = 4 + DELETE = 5 + + _RequestMethod_NAMES = { + 1: "GET", + 2: "POST", + 3: "HEAD", + 4: "PUT", + 5: "DELETE", + } + + def RequestMethod_Name(cls, x): return cls._RequestMethod_NAMES.get(x, "") + RequestMethod_Name = classmethod(RequestMethod_Name) + + has_queue_name_ = 0 + queue_name_ = "" + has_task_name_ = 0 + task_name_ = "" + has_eta_usec_ = 0 + eta_usec_ = 0 + has_method_ = 0 + method_ = 2 + has_url_ = 0 + url_ = "" + has_body_ = 0 + body_ = "" + has_transaction_ = 0 + transaction_ = None + has_app_id_ = 0 + app_id_ = "" + has_crontimetable_ = 0 + crontimetable_ = None + has_description_ = 0 + description_ = "" + has_payload_ = 0 + payload_ = None + + def __init__(self, contents=None): + self.header_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def task_name(self): return self.task_name_ + + def set_task_name(self, x): + self.has_task_name_ = 1 + self.task_name_ = x + + def clear_task_name(self): + if self.has_task_name_: + self.has_task_name_ = 0 + self.task_name_ = "" + + def has_task_name(self): return self.has_task_name_ + + def eta_usec(self): return self.eta_usec_ + + def set_eta_usec(self, x): + self.has_eta_usec_ = 1 + self.eta_usec_ = x + + def clear_eta_usec(self): + if self.has_eta_usec_: + self.has_eta_usec_ = 0 + self.eta_usec_ = 0 + + def has_eta_usec(self): return self.has_eta_usec_ + + def method(self): return self.method_ + + def set_method(self, x): + self.has_method_ = 1 + self.method_ = x + + def clear_method(self): + if self.has_method_: + self.has_method_ = 0 + self.method_ = 2 + + def has_method(self): return self.has_method_ + + def url(self): return self.url_ + + def set_url(self, x): + self.has_url_ = 1 + self.url_ = x + + def clear_url(self): + if self.has_url_: + self.has_url_ = 0 + self.url_ = "" + + def has_url(self): return self.has_url_ + + def header_size(self): return len(self.header_) + def header_list(self): return self.header_ + + def header(self, i): + return self.header_[i] + + def mutable_header(self, i): + return self.header_[i] + + def add_header(self): + x = TaskQueueAddRequest_Header() + self.header_.append(x) + return x + + def clear_header(self): + self.header_ = [] + def body(self): return self.body_ + + def set_body(self, x): + self.has_body_ = 1 + self.body_ = x + + def clear_body(self): + if self.has_body_: + self.has_body_ = 0 + self.body_ = "" + + def has_body(self): return self.has_body_ + + def transaction(self): + if self.transaction_ is None: + self.lazy_init_lock_.acquire() + try: + if self.transaction_ is None: self.transaction_ = Transaction() + finally: + self.lazy_init_lock_.release() + return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction() + + def clear_transaction(self): + if self.has_transaction_: + self.has_transaction_ = 0; + if self.transaction_ is not None: self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def crontimetable(self): + if self.crontimetable_ is None: + self.lazy_init_lock_.acquire() + try: + if self.crontimetable_ is None: self.crontimetable_ = TaskQueueAddRequest_CronTimetable() + finally: + self.lazy_init_lock_.release() + return self.crontimetable_ + + def mutable_crontimetable(self): self.has_crontimetable_ = 1; return self.crontimetable() + + def clear_crontimetable(self): + if self.has_crontimetable_: + self.has_crontimetable_ = 0; + if self.crontimetable_ is not None: self.crontimetable_.Clear() + + def has_crontimetable(self): return self.has_crontimetable_ + + def description(self): return self.description_ + + def set_description(self, x): + self.has_description_ = 1 + self.description_ = x + + def clear_description(self): + if self.has_description_: + self.has_description_ = 0 + self.description_ = "" + + def has_description(self): return self.has_description_ + + def payload(self): + if self.payload_ is None: + self.lazy_init_lock_.acquire() + try: + if self.payload_ is None: self.payload_ = MessageSet() + finally: + self.lazy_init_lock_.release() + return self.payload_ + + def mutable_payload(self): self.has_payload_ = 1; return self.payload() + + def clear_payload(self): + if self.has_payload_: + self.has_payload_ = 0; + if self.payload_ is not None: self.payload_.Clear() + + def has_payload(self): return self.has_payload_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + if (x.has_task_name()): self.set_task_name(x.task_name()) + if (x.has_eta_usec()): self.set_eta_usec(x.eta_usec()) + if (x.has_method()): self.set_method(x.method()) + if (x.has_url()): self.set_url(x.url()) + for i in xrange(x.header_size()): self.add_header().CopyFrom(x.header(i)) + if (x.has_body()): self.set_body(x.body()) + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_crontimetable()): self.mutable_crontimetable().MergeFrom(x.crontimetable()) + if (x.has_description()): self.set_description(x.description()) + if (x.has_payload()): self.mutable_payload().MergeFrom(x.payload()) + + def Equals(self, x): + if x is self: return 1 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if self.has_task_name_ != x.has_task_name_: return 0 + if self.has_task_name_ and self.task_name_ != x.task_name_: return 0 + if self.has_eta_usec_ != x.has_eta_usec_: return 0 + if self.has_eta_usec_ and self.eta_usec_ != x.eta_usec_: return 0 + if self.has_method_ != x.has_method_: return 0 + if self.has_method_ and self.method_ != x.method_: return 0 + if self.has_url_ != x.has_url_: return 0 + if self.has_url_ and self.url_ != x.url_: return 0 + if len(self.header_) != len(x.header_): return 0 + for e1, e2 in zip(self.header_, x.header_): + if e1 != e2: return 0 + if self.has_body_ != x.has_body_: return 0 + if self.has_body_ and self.body_ != x.body_: return 0 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_crontimetable_ != x.has_crontimetable_: return 0 + if self.has_crontimetable_ and self.crontimetable_ != x.crontimetable_: return 0 + if self.has_description_ != x.has_description_: return 0 + if self.has_description_ and self.description_ != x.description_: return 0 + if self.has_payload_ != x.has_payload_: return 0 + if self.has_payload_ and self.payload_ != x.payload_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + if (not self.has_task_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: task_name not set.') + if (not self.has_eta_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: eta_usec not set.') + for p in self.header_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_transaction_ and not self.transaction_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_crontimetable_ and not self.crontimetable_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_payload_ and not self.payload_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.queue_name_)) + n += self.lengthString(len(self.task_name_)) + n += self.lengthVarInt64(self.eta_usec_) + if (self.has_method_): n += 1 + self.lengthVarInt64(self.method_) + if (self.has_url_): n += 1 + self.lengthString(len(self.url_)) + n += 2 * len(self.header_) + for i in xrange(len(self.header_)): n += self.header_[i].ByteSize() + if (self.has_body_): n += 1 + self.lengthString(len(self.body_)) + if (self.has_transaction_): n += 1 + self.lengthString(self.transaction_.ByteSize()) + if (self.has_app_id_): n += 1 + self.lengthString(len(self.app_id_)) + if (self.has_crontimetable_): n += 2 + self.crontimetable_.ByteSize() + if (self.has_description_): n += 1 + self.lengthString(len(self.description_)) + if (self.has_payload_): n += 2 + self.lengthString(self.payload_.ByteSize()) + return n + 3 + + def Clear(self): + self.clear_queue_name() + self.clear_task_name() + self.clear_eta_usec() + self.clear_method() + self.clear_url() + self.clear_header() + self.clear_body() + self.clear_transaction() + self.clear_app_id() + self.clear_crontimetable() + self.clear_description() + self.clear_payload() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.queue_name_) + out.putVarInt32(18) + out.putPrefixedString(self.task_name_) + out.putVarInt32(24) + out.putVarInt64(self.eta_usec_) + if (self.has_url_): + out.putVarInt32(34) + out.putPrefixedString(self.url_) + if (self.has_method_): + out.putVarInt32(40) + out.putVarInt32(self.method_) + for i in xrange(len(self.header_)): + out.putVarInt32(51) + self.header_[i].OutputUnchecked(out) + out.putVarInt32(52) + if (self.has_body_): + out.putVarInt32(74) + out.putPrefixedString(self.body_) + if (self.has_transaction_): + out.putVarInt32(82) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + if (self.has_app_id_): + out.putVarInt32(90) + out.putPrefixedString(self.app_id_) + if (self.has_crontimetable_): + out.putVarInt32(99) + self.crontimetable_.OutputUnchecked(out) + out.putVarInt32(100) + if (self.has_description_): + out.putVarInt32(122) + out.putPrefixedString(self.description_) + if (self.has_payload_): + out.putVarInt32(130) + out.putVarInt32(self.payload_.ByteSize()) + self.payload_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 18: + self.set_task_name(d.getPrefixedString()) + continue + if tt == 24: + self.set_eta_usec(d.getVarInt64()) + continue + if tt == 34: + self.set_url(d.getPrefixedString()) + continue + if tt == 40: + self.set_method(d.getVarInt32()) + continue + if tt == 51: + self.add_header().TryMerge(d) + continue + if tt == 74: + self.set_body(d.getPrefixedString()) + continue + if tt == 82: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 90: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 99: + self.mutable_crontimetable().TryMerge(d) + continue + if tt == 122: + self.set_description(d.getPrefixedString()) + continue + if tt == 130: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_payload().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + if self.has_task_name_: res+=prefix+("task_name: %s\n" % self.DebugFormatString(self.task_name_)) + if self.has_eta_usec_: res+=prefix+("eta_usec: %s\n" % self.DebugFormatInt64(self.eta_usec_)) + if self.has_method_: res+=prefix+("method: %s\n" % self.DebugFormatInt32(self.method_)) + if self.has_url_: res+=prefix+("url: %s\n" % self.DebugFormatString(self.url_)) + cnt=0 + for e in self.header_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Header%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_body_: res+=prefix+("body: %s\n" % self.DebugFormatString(self.body_)) + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_crontimetable_: + res+=prefix+"CronTimetable {\n" + res+=self.crontimetable_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + if self.has_description_: res+=prefix+("description: %s\n" % self.DebugFormatString(self.description_)) + if self.has_payload_: + res+=prefix+"payload <\n" + res+=self.payload_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kqueue_name = 1 + ktask_name = 2 + keta_usec = 3 + kmethod = 5 + kurl = 4 + kHeaderGroup = 6 + kHeaderkey = 7 + kHeadervalue = 8 + kbody = 9 + ktransaction = 10 + kapp_id = 11 + kCronTimetableGroup = 12 + kCronTimetableschedule = 13 + kCronTimetabletimezone = 14 + kdescription = 15 + kpayload = 16 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "queue_name", + 2: "task_name", + 3: "eta_usec", + 4: "url", + 5: "method", + 6: "Header", + 7: "key", + 8: "value", + 9: "body", + 10: "transaction", + 11: "app_id", + 12: "CronTimetable", + 13: "schedule", + 14: "timezone", + 15: "description", + 16: "payload", + }, 16) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.STARTGROUP, + 7: ProtocolBuffer.Encoder.STRING, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.STRING, + 10: ProtocolBuffer.Encoder.STRING, + 11: ProtocolBuffer.Encoder.STRING, + 12: ProtocolBuffer.Encoder.STARTGROUP, + 13: ProtocolBuffer.Encoder.STRING, + 14: ProtocolBuffer.Encoder.STRING, + 15: ProtocolBuffer.Encoder.STRING, + 16: ProtocolBuffer.Encoder.STRING, + }, 16, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueAddResponse(ProtocolBuffer.ProtocolMessage): + has_chosen_task_name_ = 0 + chosen_task_name_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def chosen_task_name(self): return self.chosen_task_name_ + + def set_chosen_task_name(self, x): + self.has_chosen_task_name_ = 1 + self.chosen_task_name_ = x + + def clear_chosen_task_name(self): + if self.has_chosen_task_name_: + self.has_chosen_task_name_ = 0 + self.chosen_task_name_ = "" + + def has_chosen_task_name(self): return self.has_chosen_task_name_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_chosen_task_name()): self.set_chosen_task_name(x.chosen_task_name()) + + def Equals(self, x): + if x is self: return 1 + if self.has_chosen_task_name_ != x.has_chosen_task_name_: return 0 + if self.has_chosen_task_name_ and self.chosen_task_name_ != x.chosen_task_name_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_chosen_task_name_): n += 1 + self.lengthString(len(self.chosen_task_name_)) + return n + 0 + + def Clear(self): + self.clear_chosen_task_name() + + def OutputUnchecked(self, out): + if (self.has_chosen_task_name_): + out.putVarInt32(10) + out.putPrefixedString(self.chosen_task_name_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_chosen_task_name(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_chosen_task_name_: res+=prefix+("chosen_task_name: %s\n" % self.DebugFormatString(self.chosen_task_name_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kchosen_task_name = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "chosen_task_name", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueBulkAddRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.add_request_ = [] + if contents is not None: self.MergeFromString(contents) + + def add_request_size(self): return len(self.add_request_) + def add_request_list(self): return self.add_request_ + + def add_request(self, i): + return self.add_request_[i] + + def mutable_add_request(self, i): + return self.add_request_[i] + + def add_add_request(self): + x = TaskQueueAddRequest() + self.add_request_.append(x) + return x + + def clear_add_request(self): + self.add_request_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.add_request_size()): self.add_add_request().CopyFrom(x.add_request(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.add_request_) != len(x.add_request_): return 0 + for e1, e2 in zip(self.add_request_, x.add_request_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.add_request_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.add_request_) + for i in xrange(len(self.add_request_)): n += self.lengthString(self.add_request_[i].ByteSize()) + return n + 0 + + def Clear(self): + self.clear_add_request() + + def OutputUnchecked(self, out): + for i in xrange(len(self.add_request_)): + out.putVarInt32(10) + out.putVarInt32(self.add_request_[i].ByteSize()) + self.add_request_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_add_request().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.add_request_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("add_request%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kadd_request = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "add_request", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueBulkAddResponse_TaskResult(ProtocolBuffer.ProtocolMessage): + has_result_ = 0 + result_ = 0 + has_chosen_task_name_ = 0 + chosen_task_name_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def result(self): return self.result_ + + def set_result(self, x): + self.has_result_ = 1 + self.result_ = x + + def clear_result(self): + if self.has_result_: + self.has_result_ = 0 + self.result_ = 0 + + def has_result(self): return self.has_result_ + + def chosen_task_name(self): return self.chosen_task_name_ + + def set_chosen_task_name(self, x): + self.has_chosen_task_name_ = 1 + self.chosen_task_name_ = x + + def clear_chosen_task_name(self): + if self.has_chosen_task_name_: + self.has_chosen_task_name_ = 0 + self.chosen_task_name_ = "" + + def has_chosen_task_name(self): return self.has_chosen_task_name_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_result()): self.set_result(x.result()) + if (x.has_chosen_task_name()): self.set_chosen_task_name(x.chosen_task_name()) + + def Equals(self, x): + if x is self: return 1 + if self.has_result_ != x.has_result_: return 0 + if self.has_result_ and self.result_ != x.result_: return 0 + if self.has_chosen_task_name_ != x.has_chosen_task_name_: return 0 + if self.has_chosen_task_name_ and self.chosen_task_name_ != x.chosen_task_name_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_result_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: result not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.result_) + if (self.has_chosen_task_name_): n += 1 + self.lengthString(len(self.chosen_task_name_)) + return n + 1 + + def Clear(self): + self.clear_result() + self.clear_chosen_task_name() + + def OutputUnchecked(self, out): + out.putVarInt32(16) + out.putVarInt32(self.result_) + if (self.has_chosen_task_name_): + out.putVarInt32(26) + out.putPrefixedString(self.chosen_task_name_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 16: + self.set_result(d.getVarInt32()) + continue + if tt == 26: + self.set_chosen_task_name(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_result_: res+=prefix+("result: %s\n" % self.DebugFormatInt32(self.result_)) + if self.has_chosen_task_name_: res+=prefix+("chosen_task_name: %s\n" % self.DebugFormatString(self.chosen_task_name_)) + return res + +class TaskQueueBulkAddResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.taskresult_ = [] + if contents is not None: self.MergeFromString(contents) + + def taskresult_size(self): return len(self.taskresult_) + def taskresult_list(self): return self.taskresult_ + + def taskresult(self, i): + return self.taskresult_[i] + + def mutable_taskresult(self, i): + return self.taskresult_[i] + + def add_taskresult(self): + x = TaskQueueBulkAddResponse_TaskResult() + self.taskresult_.append(x) + return x + + def clear_taskresult(self): + self.taskresult_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.taskresult_size()): self.add_taskresult().CopyFrom(x.taskresult(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.taskresult_) != len(x.taskresult_): return 0 + for e1, e2 in zip(self.taskresult_, x.taskresult_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.taskresult_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.taskresult_) + for i in xrange(len(self.taskresult_)): n += self.taskresult_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_taskresult() + + def OutputUnchecked(self, out): + for i in xrange(len(self.taskresult_)): + out.putVarInt32(11) + self.taskresult_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_taskresult().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.taskresult_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("TaskResult%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kTaskResultGroup = 1 + kTaskResultresult = 2 + kTaskResultchosen_task_name = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "TaskResult", + 2: "result", + 3: "chosen_task_name", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueDeleteRequest(ProtocolBuffer.ProtocolMessage): + has_queue_name_ = 0 + queue_name_ = "" + has_app_id_ = 0 + app_id_ = "" + + def __init__(self, contents=None): + self.task_name_ = [] + if contents is not None: self.MergeFromString(contents) + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def task_name_size(self): return len(self.task_name_) + def task_name_list(self): return self.task_name_ + + def task_name(self, i): + return self.task_name_[i] + + def set_task_name(self, i, x): + self.task_name_[i] = x + + def add_task_name(self, x): + self.task_name_.append(x) + + def clear_task_name(self): + self.task_name_ = [] + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + for i in xrange(x.task_name_size()): self.add_task_name(x.task_name(i)) + if (x.has_app_id()): self.set_app_id(x.app_id()) + + def Equals(self, x): + if x is self: return 1 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if len(self.task_name_) != len(x.task_name_): return 0 + for e1, e2 in zip(self.task_name_, x.task_name_): + if e1 != e2: return 0 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.queue_name_)) + n += 1 * len(self.task_name_) + for i in xrange(len(self.task_name_)): n += self.lengthString(len(self.task_name_[i])) + if (self.has_app_id_): n += 1 + self.lengthString(len(self.app_id_)) + return n + 1 + + def Clear(self): + self.clear_queue_name() + self.clear_task_name() + self.clear_app_id() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.queue_name_) + for i in xrange(len(self.task_name_)): + out.putVarInt32(18) + out.putPrefixedString(self.task_name_[i]) + if (self.has_app_id_): + out.putVarInt32(26) + out.putPrefixedString(self.app_id_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 18: + self.add_task_name(d.getPrefixedString()) + continue + if tt == 26: + self.set_app_id(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + cnt=0 + for e in self.task_name_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("task_name%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kqueue_name = 1 + ktask_name = 2 + kapp_id = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "queue_name", + 2: "task_name", + 3: "app_id", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueDeleteResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.result_ = [] + if contents is not None: self.MergeFromString(contents) + + def result_size(self): return len(self.result_) + def result_list(self): return self.result_ + + def result(self, i): + return self.result_[i] + + def set_result(self, i, x): + self.result_[i] = x + + def add_result(self, x): + self.result_.append(x) + + def clear_result(self): + self.result_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.result_size()): self.add_result(x.result(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.result_) != len(x.result_): return 0 + for e1, e2 in zip(self.result_, x.result_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.result_) + for i in xrange(len(self.result_)): n += self.lengthVarInt64(self.result_[i]) + return n + 0 + + def Clear(self): + self.clear_result() + + def OutputUnchecked(self, out): + for i in xrange(len(self.result_)): + out.putVarInt32(24) + out.putVarInt32(self.result_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 24: + self.add_result(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.result_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("result%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kresult = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 3: "result", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueUpdateQueueRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_queue_name_ = 0 + queue_name_ = "" + has_bucket_refill_per_second_ = 0 + bucket_refill_per_second_ = 0.0 + has_bucket_capacity_ = 0 + bucket_capacity_ = 0 + has_user_specified_rate_ = 0 + user_specified_rate_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def bucket_refill_per_second(self): return self.bucket_refill_per_second_ + + def set_bucket_refill_per_second(self, x): + self.has_bucket_refill_per_second_ = 1 + self.bucket_refill_per_second_ = x + + def clear_bucket_refill_per_second(self): + if self.has_bucket_refill_per_second_: + self.has_bucket_refill_per_second_ = 0 + self.bucket_refill_per_second_ = 0.0 + + def has_bucket_refill_per_second(self): return self.has_bucket_refill_per_second_ + + def bucket_capacity(self): return self.bucket_capacity_ + + def set_bucket_capacity(self, x): + self.has_bucket_capacity_ = 1 + self.bucket_capacity_ = x + + def clear_bucket_capacity(self): + if self.has_bucket_capacity_: + self.has_bucket_capacity_ = 0 + self.bucket_capacity_ = 0 + + def has_bucket_capacity(self): return self.has_bucket_capacity_ + + def user_specified_rate(self): return self.user_specified_rate_ + + def set_user_specified_rate(self, x): + self.has_user_specified_rate_ = 1 + self.user_specified_rate_ = x + + def clear_user_specified_rate(self): + if self.has_user_specified_rate_: + self.has_user_specified_rate_ = 0 + self.user_specified_rate_ = "" + + def has_user_specified_rate(self): return self.has_user_specified_rate_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + if (x.has_bucket_refill_per_second()): self.set_bucket_refill_per_second(x.bucket_refill_per_second()) + if (x.has_bucket_capacity()): self.set_bucket_capacity(x.bucket_capacity()) + if (x.has_user_specified_rate()): self.set_user_specified_rate(x.user_specified_rate()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if self.has_bucket_refill_per_second_ != x.has_bucket_refill_per_second_: return 0 + if self.has_bucket_refill_per_second_ and self.bucket_refill_per_second_ != x.bucket_refill_per_second_: return 0 + if self.has_bucket_capacity_ != x.has_bucket_capacity_: return 0 + if self.has_bucket_capacity_ and self.bucket_capacity_ != x.bucket_capacity_: return 0 + if self.has_user_specified_rate_ != x.has_user_specified_rate_: return 0 + if self.has_user_specified_rate_ and self.user_specified_rate_ != x.user_specified_rate_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + if (not self.has_bucket_refill_per_second_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: bucket_refill_per_second not set.') + if (not self.has_bucket_capacity_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: bucket_capacity not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthString(len(self.queue_name_)) + n += self.lengthVarInt64(self.bucket_capacity_) + if (self.has_user_specified_rate_): n += 1 + self.lengthString(len(self.user_specified_rate_)) + return n + 12 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + self.clear_bucket_refill_per_second() + self.clear_bucket_capacity() + self.clear_user_specified_rate() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + out.putVarInt32(25) + out.putDouble(self.bucket_refill_per_second_) + out.putVarInt32(32) + out.putVarInt32(self.bucket_capacity_) + if (self.has_user_specified_rate_): + out.putVarInt32(42) + out.putPrefixedString(self.user_specified_rate_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 25: + self.set_bucket_refill_per_second(d.getDouble()) + continue + if tt == 32: + self.set_bucket_capacity(d.getVarInt32()) + continue + if tt == 42: + self.set_user_specified_rate(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + if self.has_bucket_refill_per_second_: res+=prefix+("bucket_refill_per_second: %s\n" % self.DebugFormat(self.bucket_refill_per_second_)) + if self.has_bucket_capacity_: res+=prefix+("bucket_capacity: %s\n" % self.DebugFormatInt32(self.bucket_capacity_)) + if self.has_user_specified_rate_: res+=prefix+("user_specified_rate: %s\n" % self.DebugFormatString(self.user_specified_rate_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + kbucket_refill_per_second = 3 + kbucket_capacity = 4 + kuser_specified_rate = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + 3: "bucket_refill_per_second", + 4: "bucket_capacity", + 5: "user_specified_rate", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.DOUBLE, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueUpdateQueueResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueFetchQueuesRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_max_rows_ = 0 + max_rows_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def max_rows(self): return self.max_rows_ + + def set_max_rows(self, x): + self.has_max_rows_ = 1 + self.max_rows_ = x + + def clear_max_rows(self): + if self.has_max_rows_: + self.has_max_rows_ = 0 + self.max_rows_ = 0 + + def has_max_rows(self): return self.has_max_rows_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_max_rows()): self.set_max_rows(x.max_rows()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_max_rows_ != x.has_max_rows_: return 0 + if self.has_max_rows_ and self.max_rows_ != x.max_rows_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_max_rows_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: max_rows not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthVarInt64(self.max_rows_) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_max_rows() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(16) + out.putVarInt32(self.max_rows_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 16: + self.set_max_rows(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_max_rows_: res+=prefix+("max_rows: %s\n" % self.DebugFormatInt32(self.max_rows_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kmax_rows = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "max_rows", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueFetchQueuesResponse_Queue(ProtocolBuffer.ProtocolMessage): + has_queue_name_ = 0 + queue_name_ = "" + has_bucket_refill_per_second_ = 0 + bucket_refill_per_second_ = 0.0 + has_bucket_capacity_ = 0 + bucket_capacity_ = 0.0 + has_user_specified_rate_ = 0 + user_specified_rate_ = "" + has_paused_ = 0 + paused_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def bucket_refill_per_second(self): return self.bucket_refill_per_second_ + + def set_bucket_refill_per_second(self, x): + self.has_bucket_refill_per_second_ = 1 + self.bucket_refill_per_second_ = x + + def clear_bucket_refill_per_second(self): + if self.has_bucket_refill_per_second_: + self.has_bucket_refill_per_second_ = 0 + self.bucket_refill_per_second_ = 0.0 + + def has_bucket_refill_per_second(self): return self.has_bucket_refill_per_second_ + + def bucket_capacity(self): return self.bucket_capacity_ + + def set_bucket_capacity(self, x): + self.has_bucket_capacity_ = 1 + self.bucket_capacity_ = x + + def clear_bucket_capacity(self): + if self.has_bucket_capacity_: + self.has_bucket_capacity_ = 0 + self.bucket_capacity_ = 0.0 + + def has_bucket_capacity(self): return self.has_bucket_capacity_ + + def user_specified_rate(self): return self.user_specified_rate_ + + def set_user_specified_rate(self, x): + self.has_user_specified_rate_ = 1 + self.user_specified_rate_ = x + + def clear_user_specified_rate(self): + if self.has_user_specified_rate_: + self.has_user_specified_rate_ = 0 + self.user_specified_rate_ = "" + + def has_user_specified_rate(self): return self.has_user_specified_rate_ + + def paused(self): return self.paused_ + + def set_paused(self, x): + self.has_paused_ = 1 + self.paused_ = x + + def clear_paused(self): + if self.has_paused_: + self.has_paused_ = 0 + self.paused_ = 0 + + def has_paused(self): return self.has_paused_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + if (x.has_bucket_refill_per_second()): self.set_bucket_refill_per_second(x.bucket_refill_per_second()) + if (x.has_bucket_capacity()): self.set_bucket_capacity(x.bucket_capacity()) + if (x.has_user_specified_rate()): self.set_user_specified_rate(x.user_specified_rate()) + if (x.has_paused()): self.set_paused(x.paused()) + + def Equals(self, x): + if x is self: return 1 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if self.has_bucket_refill_per_second_ != x.has_bucket_refill_per_second_: return 0 + if self.has_bucket_refill_per_second_ and self.bucket_refill_per_second_ != x.bucket_refill_per_second_: return 0 + if self.has_bucket_capacity_ != x.has_bucket_capacity_: return 0 + if self.has_bucket_capacity_ and self.bucket_capacity_ != x.bucket_capacity_: return 0 + if self.has_user_specified_rate_ != x.has_user_specified_rate_: return 0 + if self.has_user_specified_rate_ and self.user_specified_rate_ != x.user_specified_rate_: return 0 + if self.has_paused_ != x.has_paused_: return 0 + if self.has_paused_ and self.paused_ != x.paused_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + if (not self.has_bucket_refill_per_second_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: bucket_refill_per_second not set.') + if (not self.has_bucket_capacity_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: bucket_capacity not set.') + if (not self.has_paused_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: paused not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.queue_name_)) + if (self.has_user_specified_rate_): n += 1 + self.lengthString(len(self.user_specified_rate_)) + return n + 21 + + def Clear(self): + self.clear_queue_name() + self.clear_bucket_refill_per_second() + self.clear_bucket_capacity() + self.clear_user_specified_rate() + self.clear_paused() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + out.putVarInt32(25) + out.putDouble(self.bucket_refill_per_second_) + out.putVarInt32(33) + out.putDouble(self.bucket_capacity_) + if (self.has_user_specified_rate_): + out.putVarInt32(42) + out.putPrefixedString(self.user_specified_rate_) + out.putVarInt32(48) + out.putBoolean(self.paused_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 25: + self.set_bucket_refill_per_second(d.getDouble()) + continue + if tt == 33: + self.set_bucket_capacity(d.getDouble()) + continue + if tt == 42: + self.set_user_specified_rate(d.getPrefixedString()) + continue + if tt == 48: + self.set_paused(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + if self.has_bucket_refill_per_second_: res+=prefix+("bucket_refill_per_second: %s\n" % self.DebugFormat(self.bucket_refill_per_second_)) + if self.has_bucket_capacity_: res+=prefix+("bucket_capacity: %s\n" % self.DebugFormat(self.bucket_capacity_)) + if self.has_user_specified_rate_: res+=prefix+("user_specified_rate: %s\n" % self.DebugFormatString(self.user_specified_rate_)) + if self.has_paused_: res+=prefix+("paused: %s\n" % self.DebugFormatBool(self.paused_)) + return res + +class TaskQueueFetchQueuesResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.queue_ = [] + if contents is not None: self.MergeFromString(contents) + + def queue_size(self): return len(self.queue_) + def queue_list(self): return self.queue_ + + def queue(self, i): + return self.queue_[i] + + def mutable_queue(self, i): + return self.queue_[i] + + def add_queue(self): + x = TaskQueueFetchQueuesResponse_Queue() + self.queue_.append(x) + return x + + def clear_queue(self): + self.queue_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.queue_size()): self.add_queue().CopyFrom(x.queue(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.queue_) != len(x.queue_): return 0 + for e1, e2 in zip(self.queue_, x.queue_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.queue_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.queue_) + for i in xrange(len(self.queue_)): n += self.queue_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_queue() + + def OutputUnchecked(self, out): + for i in xrange(len(self.queue_)): + out.putVarInt32(11) + self.queue_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_queue().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.queue_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Queue%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kQueueGroup = 1 + kQueuequeue_name = 2 + kQueuebucket_refill_per_second = 3 + kQueuebucket_capacity = 4 + kQueueuser_specified_rate = 5 + kQueuepaused = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Queue", + 2: "queue_name", + 3: "bucket_refill_per_second", + 4: "bucket_capacity", + 5: "user_specified_rate", + 6: "paused", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.DOUBLE, + 4: ProtocolBuffer.Encoder.DOUBLE, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.NUMERIC, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueFetchQueueStatsRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_max_num_tasks_ = 0 + max_num_tasks_ = 0 + + def __init__(self, contents=None): + self.queue_name_ = [] + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name_size(self): return len(self.queue_name_) + def queue_name_list(self): return self.queue_name_ + + def queue_name(self, i): + return self.queue_name_[i] + + def set_queue_name(self, i, x): + self.queue_name_[i] = x + + def add_queue_name(self, x): + self.queue_name_.append(x) + + def clear_queue_name(self): + self.queue_name_ = [] + + def max_num_tasks(self): return self.max_num_tasks_ + + def set_max_num_tasks(self, x): + self.has_max_num_tasks_ = 1 + self.max_num_tasks_ = x + + def clear_max_num_tasks(self): + if self.has_max_num_tasks_: + self.has_max_num_tasks_ = 0 + self.max_num_tasks_ = 0 + + def has_max_num_tasks(self): return self.has_max_num_tasks_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + for i in xrange(x.queue_name_size()): self.add_queue_name(x.queue_name(i)) + if (x.has_max_num_tasks()): self.set_max_num_tasks(x.max_num_tasks()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if len(self.queue_name_) != len(x.queue_name_): return 0 + for e1, e2 in zip(self.queue_name_, x.queue_name_): + if e1 != e2: return 0 + if self.has_max_num_tasks_ != x.has_max_num_tasks_: return 0 + if self.has_max_num_tasks_ and self.max_num_tasks_ != x.max_num_tasks_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_max_num_tasks_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: max_num_tasks not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += 1 * len(self.queue_name_) + for i in xrange(len(self.queue_name_)): n += self.lengthString(len(self.queue_name_[i])) + n += self.lengthVarInt64(self.max_num_tasks_) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + self.clear_max_num_tasks() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + for i in xrange(len(self.queue_name_)): + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_[i]) + out.putVarInt32(24) + out.putVarInt32(self.max_num_tasks_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.add_queue_name(d.getPrefixedString()) + continue + if tt == 24: + self.set_max_num_tasks(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + cnt=0 + for e in self.queue_name_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("queue_name%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_max_num_tasks_: res+=prefix+("max_num_tasks: %s\n" % self.DebugFormatInt32(self.max_num_tasks_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + kmax_num_tasks = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + 3: "max_num_tasks", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueScannerQueueInfo(ProtocolBuffer.ProtocolMessage): + has_executed_last_minute_ = 0 + executed_last_minute_ = 0 + has_executed_last_hour_ = 0 + executed_last_hour_ = 0 + has_sampling_duration_seconds_ = 0 + sampling_duration_seconds_ = 0.0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def executed_last_minute(self): return self.executed_last_minute_ + + def set_executed_last_minute(self, x): + self.has_executed_last_minute_ = 1 + self.executed_last_minute_ = x + + def clear_executed_last_minute(self): + if self.has_executed_last_minute_: + self.has_executed_last_minute_ = 0 + self.executed_last_minute_ = 0 + + def has_executed_last_minute(self): return self.has_executed_last_minute_ + + def executed_last_hour(self): return self.executed_last_hour_ + + def set_executed_last_hour(self, x): + self.has_executed_last_hour_ = 1 + self.executed_last_hour_ = x + + def clear_executed_last_hour(self): + if self.has_executed_last_hour_: + self.has_executed_last_hour_ = 0 + self.executed_last_hour_ = 0 + + def has_executed_last_hour(self): return self.has_executed_last_hour_ + + def sampling_duration_seconds(self): return self.sampling_duration_seconds_ + + def set_sampling_duration_seconds(self, x): + self.has_sampling_duration_seconds_ = 1 + self.sampling_duration_seconds_ = x + + def clear_sampling_duration_seconds(self): + if self.has_sampling_duration_seconds_: + self.has_sampling_duration_seconds_ = 0 + self.sampling_duration_seconds_ = 0.0 + + def has_sampling_duration_seconds(self): return self.has_sampling_duration_seconds_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_executed_last_minute()): self.set_executed_last_minute(x.executed_last_minute()) + if (x.has_executed_last_hour()): self.set_executed_last_hour(x.executed_last_hour()) + if (x.has_sampling_duration_seconds()): self.set_sampling_duration_seconds(x.sampling_duration_seconds()) + + def Equals(self, x): + if x is self: return 1 + if self.has_executed_last_minute_ != x.has_executed_last_minute_: return 0 + if self.has_executed_last_minute_ and self.executed_last_minute_ != x.executed_last_minute_: return 0 + if self.has_executed_last_hour_ != x.has_executed_last_hour_: return 0 + if self.has_executed_last_hour_ and self.executed_last_hour_ != x.executed_last_hour_: return 0 + if self.has_sampling_duration_seconds_ != x.has_sampling_duration_seconds_: return 0 + if self.has_sampling_duration_seconds_ and self.sampling_duration_seconds_ != x.sampling_duration_seconds_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_executed_last_minute_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: executed_last_minute not set.') + if (not self.has_executed_last_hour_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: executed_last_hour not set.') + if (not self.has_sampling_duration_seconds_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: sampling_duration_seconds not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.executed_last_minute_) + n += self.lengthVarInt64(self.executed_last_hour_) + return n + 11 + + def Clear(self): + self.clear_executed_last_minute() + self.clear_executed_last_hour() + self.clear_sampling_duration_seconds() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.executed_last_minute_) + out.putVarInt32(16) + out.putVarInt64(self.executed_last_hour_) + out.putVarInt32(25) + out.putDouble(self.sampling_duration_seconds_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_executed_last_minute(d.getVarInt64()) + continue + if tt == 16: + self.set_executed_last_hour(d.getVarInt64()) + continue + if tt == 25: + self.set_sampling_duration_seconds(d.getDouble()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_executed_last_minute_: res+=prefix+("executed_last_minute: %s\n" % self.DebugFormatInt64(self.executed_last_minute_)) + if self.has_executed_last_hour_: res+=prefix+("executed_last_hour: %s\n" % self.DebugFormatInt64(self.executed_last_hour_)) + if self.has_sampling_duration_seconds_: res+=prefix+("sampling_duration_seconds: %s\n" % self.DebugFormat(self.sampling_duration_seconds_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kexecuted_last_minute = 1 + kexecuted_last_hour = 2 + ksampling_duration_seconds = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "executed_last_minute", + 2: "executed_last_hour", + 3: "sampling_duration_seconds", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.DOUBLE, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueFetchQueueStatsResponse_QueueStats(ProtocolBuffer.ProtocolMessage): + has_num_tasks_ = 0 + num_tasks_ = 0 + has_oldest_eta_usec_ = 0 + oldest_eta_usec_ = 0 + has_scanner_info_ = 0 + scanner_info_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def num_tasks(self): return self.num_tasks_ + + def set_num_tasks(self, x): + self.has_num_tasks_ = 1 + self.num_tasks_ = x + + def clear_num_tasks(self): + if self.has_num_tasks_: + self.has_num_tasks_ = 0 + self.num_tasks_ = 0 + + def has_num_tasks(self): return self.has_num_tasks_ + + def oldest_eta_usec(self): return self.oldest_eta_usec_ + + def set_oldest_eta_usec(self, x): + self.has_oldest_eta_usec_ = 1 + self.oldest_eta_usec_ = x + + def clear_oldest_eta_usec(self): + if self.has_oldest_eta_usec_: + self.has_oldest_eta_usec_ = 0 + self.oldest_eta_usec_ = 0 + + def has_oldest_eta_usec(self): return self.has_oldest_eta_usec_ + + def scanner_info(self): + if self.scanner_info_ is None: + self.lazy_init_lock_.acquire() + try: + if self.scanner_info_ is None: self.scanner_info_ = TaskQueueScannerQueueInfo() + finally: + self.lazy_init_lock_.release() + return self.scanner_info_ + + def mutable_scanner_info(self): self.has_scanner_info_ = 1; return self.scanner_info() + + def clear_scanner_info(self): + if self.has_scanner_info_: + self.has_scanner_info_ = 0; + if self.scanner_info_ is not None: self.scanner_info_.Clear() + + def has_scanner_info(self): return self.has_scanner_info_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_num_tasks()): self.set_num_tasks(x.num_tasks()) + if (x.has_oldest_eta_usec()): self.set_oldest_eta_usec(x.oldest_eta_usec()) + if (x.has_scanner_info()): self.mutable_scanner_info().MergeFrom(x.scanner_info()) + + def Equals(self, x): + if x is self: return 1 + if self.has_num_tasks_ != x.has_num_tasks_: return 0 + if self.has_num_tasks_ and self.num_tasks_ != x.num_tasks_: return 0 + if self.has_oldest_eta_usec_ != x.has_oldest_eta_usec_: return 0 + if self.has_oldest_eta_usec_ and self.oldest_eta_usec_ != x.oldest_eta_usec_: return 0 + if self.has_scanner_info_ != x.has_scanner_info_: return 0 + if self.has_scanner_info_ and self.scanner_info_ != x.scanner_info_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_num_tasks_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: num_tasks not set.') + if (not self.has_oldest_eta_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: oldest_eta_usec not set.') + if (self.has_scanner_info_ and not self.scanner_info_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.num_tasks_) + n += self.lengthVarInt64(self.oldest_eta_usec_) + if (self.has_scanner_info_): n += 1 + self.lengthString(self.scanner_info_.ByteSize()) + return n + 2 + + def Clear(self): + self.clear_num_tasks() + self.clear_oldest_eta_usec() + self.clear_scanner_info() + + def OutputUnchecked(self, out): + out.putVarInt32(16) + out.putVarInt32(self.num_tasks_) + out.putVarInt32(24) + out.putVarInt64(self.oldest_eta_usec_) + if (self.has_scanner_info_): + out.putVarInt32(34) + out.putVarInt32(self.scanner_info_.ByteSize()) + self.scanner_info_.OutputUnchecked(out) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 16: + self.set_num_tasks(d.getVarInt32()) + continue + if tt == 24: + self.set_oldest_eta_usec(d.getVarInt64()) + continue + if tt == 34: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_scanner_info().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_num_tasks_: res+=prefix+("num_tasks: %s\n" % self.DebugFormatInt32(self.num_tasks_)) + if self.has_oldest_eta_usec_: res+=prefix+("oldest_eta_usec: %s\n" % self.DebugFormatInt64(self.oldest_eta_usec_)) + if self.has_scanner_info_: + res+=prefix+"scanner_info <\n" + res+=self.scanner_info_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + +class TaskQueueFetchQueueStatsResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.queuestats_ = [] + if contents is not None: self.MergeFromString(contents) + + def queuestats_size(self): return len(self.queuestats_) + def queuestats_list(self): return self.queuestats_ + + def queuestats(self, i): + return self.queuestats_[i] + + def mutable_queuestats(self, i): + return self.queuestats_[i] + + def add_queuestats(self): + x = TaskQueueFetchQueueStatsResponse_QueueStats() + self.queuestats_.append(x) + return x + + def clear_queuestats(self): + self.queuestats_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.queuestats_size()): self.add_queuestats().CopyFrom(x.queuestats(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.queuestats_) != len(x.queuestats_): return 0 + for e1, e2 in zip(self.queuestats_, x.queuestats_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.queuestats_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.queuestats_) + for i in xrange(len(self.queuestats_)): n += self.queuestats_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_queuestats() + + def OutputUnchecked(self, out): + for i in xrange(len(self.queuestats_)): + out.putVarInt32(11) + self.queuestats_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_queuestats().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.queuestats_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("QueueStats%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kQueueStatsGroup = 1 + kQueueStatsnum_tasks = 2 + kQueueStatsoldest_eta_usec = 3 + kQueueStatsscanner_info = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "QueueStats", + 2: "num_tasks", + 3: "oldest_eta_usec", + 4: "scanner_info", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueuePauseQueueRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_queue_name_ = 0 + queue_name_ = "" + has_pause_ = 0 + pause_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def pause(self): return self.pause_ + + def set_pause(self, x): + self.has_pause_ = 1 + self.pause_ = x + + def clear_pause(self): + if self.has_pause_: + self.has_pause_ = 0 + self.pause_ = 0 + + def has_pause(self): return self.has_pause_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + if (x.has_pause()): self.set_pause(x.pause()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if self.has_pause_ != x.has_pause_: return 0 + if self.has_pause_ and self.pause_ != x.pause_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + if (not self.has_pause_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: pause not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthString(len(self.queue_name_)) + return n + 4 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + self.clear_pause() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + out.putVarInt32(24) + out.putBoolean(self.pause_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 24: + self.set_pause(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + if self.has_pause_: res+=prefix+("pause: %s\n" % self.DebugFormatBool(self.pause_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + kpause = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + 3: "pause", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueuePauseQueueResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueuePurgeQueueRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_queue_name_ = 0 + queue_name_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthString(len(self.queue_name_)) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueuePurgeQueueResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueDeleteQueueRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_queue_name_ = 0 + queue_name_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthString(len(self.queue_name_)) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueDeleteQueueResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueQueryTasksRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_queue_name_ = 0 + queue_name_ = "" + has_start_task_name_ = 0 + start_task_name_ = "" + has_start_eta_usec_ = 0 + start_eta_usec_ = 0 + has_max_rows_ = 0 + max_rows_ = 1 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def queue_name(self): return self.queue_name_ + + def set_queue_name(self, x): + self.has_queue_name_ = 1 + self.queue_name_ = x + + def clear_queue_name(self): + if self.has_queue_name_: + self.has_queue_name_ = 0 + self.queue_name_ = "" + + def has_queue_name(self): return self.has_queue_name_ + + def start_task_name(self): return self.start_task_name_ + + def set_start_task_name(self, x): + self.has_start_task_name_ = 1 + self.start_task_name_ = x + + def clear_start_task_name(self): + if self.has_start_task_name_: + self.has_start_task_name_ = 0 + self.start_task_name_ = "" + + def has_start_task_name(self): return self.has_start_task_name_ + + def start_eta_usec(self): return self.start_eta_usec_ + + def set_start_eta_usec(self, x): + self.has_start_eta_usec_ = 1 + self.start_eta_usec_ = x + + def clear_start_eta_usec(self): + if self.has_start_eta_usec_: + self.has_start_eta_usec_ = 0 + self.start_eta_usec_ = 0 + + def has_start_eta_usec(self): return self.has_start_eta_usec_ + + def max_rows(self): return self.max_rows_ + + def set_max_rows(self, x): + self.has_max_rows_ = 1 + self.max_rows_ = x + + def clear_max_rows(self): + if self.has_max_rows_: + self.has_max_rows_ = 0 + self.max_rows_ = 1 + + def has_max_rows(self): return self.has_max_rows_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_queue_name()): self.set_queue_name(x.queue_name()) + if (x.has_start_task_name()): self.set_start_task_name(x.start_task_name()) + if (x.has_start_eta_usec()): self.set_start_eta_usec(x.start_eta_usec()) + if (x.has_max_rows()): self.set_max_rows(x.max_rows()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_queue_name_ != x.has_queue_name_: return 0 + if self.has_queue_name_ and self.queue_name_ != x.queue_name_: return 0 + if self.has_start_task_name_ != x.has_start_task_name_: return 0 + if self.has_start_task_name_ and self.start_task_name_ != x.start_task_name_: return 0 + if self.has_start_eta_usec_ != x.has_start_eta_usec_: return 0 + if self.has_start_eta_usec_ and self.start_eta_usec_ != x.start_eta_usec_: return 0 + if self.has_max_rows_ != x.has_max_rows_: return 0 + if self.has_max_rows_ and self.max_rows_ != x.max_rows_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_queue_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: queue_name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthString(len(self.queue_name_)) + if (self.has_start_task_name_): n += 1 + self.lengthString(len(self.start_task_name_)) + if (self.has_start_eta_usec_): n += 1 + self.lengthVarInt64(self.start_eta_usec_) + if (self.has_max_rows_): n += 1 + self.lengthVarInt64(self.max_rows_) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_queue_name() + self.clear_start_task_name() + self.clear_start_eta_usec() + self.clear_max_rows() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(18) + out.putPrefixedString(self.queue_name_) + if (self.has_start_task_name_): + out.putVarInt32(26) + out.putPrefixedString(self.start_task_name_) + if (self.has_start_eta_usec_): + out.putVarInt32(32) + out.putVarInt64(self.start_eta_usec_) + if (self.has_max_rows_): + out.putVarInt32(40) + out.putVarInt32(self.max_rows_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_queue_name(d.getPrefixedString()) + continue + if tt == 26: + self.set_start_task_name(d.getPrefixedString()) + continue + if tt == 32: + self.set_start_eta_usec(d.getVarInt64()) + continue + if tt == 40: + self.set_max_rows(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_queue_name_: res+=prefix+("queue_name: %s\n" % self.DebugFormatString(self.queue_name_)) + if self.has_start_task_name_: res+=prefix+("start_task_name: %s\n" % self.DebugFormatString(self.start_task_name_)) + if self.has_start_eta_usec_: res+=prefix+("start_eta_usec: %s\n" % self.DebugFormatInt64(self.start_eta_usec_)) + if self.has_max_rows_: res+=prefix+("max_rows: %s\n" % self.DebugFormatInt32(self.max_rows_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kqueue_name = 2 + kstart_task_name = 3 + kstart_eta_usec = 4 + kmax_rows = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "queue_name", + 3: "start_task_name", + 4: "start_eta_usec", + 5: "max_rows", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.NUMERIC, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueQueryTasksResponse_TaskHeader(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(66) + out.putPrefixedString(self.key_) + out.putVarInt32(74) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 60: break + if tt == 66: + self.set_key(d.getPrefixedString()) + continue + if tt == 74: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + return res + +class TaskQueueQueryTasksResponse_TaskCronTimetable(ProtocolBuffer.ProtocolMessage): + has_schedule_ = 0 + schedule_ = "" + has_timezone_ = 0 + timezone_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def schedule(self): return self.schedule_ + + def set_schedule(self, x): + self.has_schedule_ = 1 + self.schedule_ = x + + def clear_schedule(self): + if self.has_schedule_: + self.has_schedule_ = 0 + self.schedule_ = "" + + def has_schedule(self): return self.has_schedule_ + + def timezone(self): return self.timezone_ + + def set_timezone(self, x): + self.has_timezone_ = 1 + self.timezone_ = x + + def clear_timezone(self): + if self.has_timezone_: + self.has_timezone_ = 0 + self.timezone_ = "" + + def has_timezone(self): return self.has_timezone_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_schedule()): self.set_schedule(x.schedule()) + if (x.has_timezone()): self.set_timezone(x.timezone()) + + def Equals(self, x): + if x is self: return 1 + if self.has_schedule_ != x.has_schedule_: return 0 + if self.has_schedule_ and self.schedule_ != x.schedule_: return 0 + if self.has_timezone_ != x.has_timezone_: return 0 + if self.has_timezone_ and self.timezone_ != x.timezone_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_schedule_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: schedule not set.') + if (not self.has_timezone_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: timezone not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.schedule_)) + n += self.lengthString(len(self.timezone_)) + return n + 2 + + def Clear(self): + self.clear_schedule() + self.clear_timezone() + + def OutputUnchecked(self, out): + out.putVarInt32(114) + out.putPrefixedString(self.schedule_) + out.putVarInt32(122) + out.putPrefixedString(self.timezone_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 108: break + if tt == 114: + self.set_schedule(d.getPrefixedString()) + continue + if tt == 122: + self.set_timezone(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_schedule_: res+=prefix+("schedule: %s\n" % self.DebugFormatString(self.schedule_)) + if self.has_timezone_: res+=prefix+("timezone: %s\n" % self.DebugFormatString(self.timezone_)) + return res + +class TaskQueueQueryTasksResponse_TaskRunLog(ProtocolBuffer.ProtocolMessage): + has_dispatched_usec_ = 0 + dispatched_usec_ = 0 + has_lag_usec_ = 0 + lag_usec_ = 0 + has_elapsed_usec_ = 0 + elapsed_usec_ = 0 + has_response_code_ = 0 + response_code_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def dispatched_usec(self): return self.dispatched_usec_ + + def set_dispatched_usec(self, x): + self.has_dispatched_usec_ = 1 + self.dispatched_usec_ = x + + def clear_dispatched_usec(self): + if self.has_dispatched_usec_: + self.has_dispatched_usec_ = 0 + self.dispatched_usec_ = 0 + + def has_dispatched_usec(self): return self.has_dispatched_usec_ + + def lag_usec(self): return self.lag_usec_ + + def set_lag_usec(self, x): + self.has_lag_usec_ = 1 + self.lag_usec_ = x + + def clear_lag_usec(self): + if self.has_lag_usec_: + self.has_lag_usec_ = 0 + self.lag_usec_ = 0 + + def has_lag_usec(self): return self.has_lag_usec_ + + def elapsed_usec(self): return self.elapsed_usec_ + + def set_elapsed_usec(self, x): + self.has_elapsed_usec_ = 1 + self.elapsed_usec_ = x + + def clear_elapsed_usec(self): + if self.has_elapsed_usec_: + self.has_elapsed_usec_ = 0 + self.elapsed_usec_ = 0 + + def has_elapsed_usec(self): return self.has_elapsed_usec_ + + def response_code(self): return self.response_code_ + + def set_response_code(self, x): + self.has_response_code_ = 1 + self.response_code_ = x + + def clear_response_code(self): + if self.has_response_code_: + self.has_response_code_ = 0 + self.response_code_ = 0 + + def has_response_code(self): return self.has_response_code_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_dispatched_usec()): self.set_dispatched_usec(x.dispatched_usec()) + if (x.has_lag_usec()): self.set_lag_usec(x.lag_usec()) + if (x.has_elapsed_usec()): self.set_elapsed_usec(x.elapsed_usec()) + if (x.has_response_code()): self.set_response_code(x.response_code()) + + def Equals(self, x): + if x is self: return 1 + if self.has_dispatched_usec_ != x.has_dispatched_usec_: return 0 + if self.has_dispatched_usec_ and self.dispatched_usec_ != x.dispatched_usec_: return 0 + if self.has_lag_usec_ != x.has_lag_usec_: return 0 + if self.has_lag_usec_ and self.lag_usec_ != x.lag_usec_: return 0 + if self.has_elapsed_usec_ != x.has_elapsed_usec_: return 0 + if self.has_elapsed_usec_ and self.elapsed_usec_ != x.elapsed_usec_: return 0 + if self.has_response_code_ != x.has_response_code_: return 0 + if self.has_response_code_ and self.response_code_ != x.response_code_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_dispatched_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: dispatched_usec not set.') + if (not self.has_lag_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: lag_usec not set.') + if (not self.has_elapsed_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: elapsed_usec not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.dispatched_usec_) + n += self.lengthVarInt64(self.lag_usec_) + n += self.lengthVarInt64(self.elapsed_usec_) + if (self.has_response_code_): n += 2 + self.lengthVarInt64(self.response_code_) + return n + 6 + + def Clear(self): + self.clear_dispatched_usec() + self.clear_lag_usec() + self.clear_elapsed_usec() + self.clear_response_code() + + def OutputUnchecked(self, out): + out.putVarInt32(136) + out.putVarInt64(self.dispatched_usec_) + out.putVarInt32(144) + out.putVarInt64(self.lag_usec_) + out.putVarInt32(152) + out.putVarInt64(self.elapsed_usec_) + if (self.has_response_code_): + out.putVarInt32(160) + out.putVarInt64(self.response_code_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 132: break + if tt == 136: + self.set_dispatched_usec(d.getVarInt64()) + continue + if tt == 144: + self.set_lag_usec(d.getVarInt64()) + continue + if tt == 152: + self.set_elapsed_usec(d.getVarInt64()) + continue + if tt == 160: + self.set_response_code(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_dispatched_usec_: res+=prefix+("dispatched_usec: %s\n" % self.DebugFormatInt64(self.dispatched_usec_)) + if self.has_lag_usec_: res+=prefix+("lag_usec: %s\n" % self.DebugFormatInt64(self.lag_usec_)) + if self.has_elapsed_usec_: res+=prefix+("elapsed_usec: %s\n" % self.DebugFormatInt64(self.elapsed_usec_)) + if self.has_response_code_: res+=prefix+("response_code: %s\n" % self.DebugFormatInt64(self.response_code_)) + return res + +class TaskQueueQueryTasksResponse_Task(ProtocolBuffer.ProtocolMessage): + + GET = 1 + POST = 2 + HEAD = 3 + PUT = 4 + DELETE = 5 + + _RequestMethod_NAMES = { + 1: "GET", + 2: "POST", + 3: "HEAD", + 4: "PUT", + 5: "DELETE", + } + + def RequestMethod_Name(cls, x): return cls._RequestMethod_NAMES.get(x, "") + RequestMethod_Name = classmethod(RequestMethod_Name) + + has_task_name_ = 0 + task_name_ = "" + has_eta_usec_ = 0 + eta_usec_ = 0 + has_url_ = 0 + url_ = "" + has_method_ = 0 + method_ = 0 + has_retry_count_ = 0 + retry_count_ = 0 + has_body_size_ = 0 + body_size_ = 0 + has_body_ = 0 + body_ = "" + has_creation_time_usec_ = 0 + creation_time_usec_ = 0 + has_crontimetable_ = 0 + crontimetable_ = None + has_runlog_ = 0 + runlog_ = None + has_description_ = 0 + description_ = "" + has_payload_ = 0 + payload_ = None + + def __init__(self, contents=None): + self.header_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def task_name(self): return self.task_name_ + + def set_task_name(self, x): + self.has_task_name_ = 1 + self.task_name_ = x + + def clear_task_name(self): + if self.has_task_name_: + self.has_task_name_ = 0 + self.task_name_ = "" + + def has_task_name(self): return self.has_task_name_ + + def eta_usec(self): return self.eta_usec_ + + def set_eta_usec(self, x): + self.has_eta_usec_ = 1 + self.eta_usec_ = x + + def clear_eta_usec(self): + if self.has_eta_usec_: + self.has_eta_usec_ = 0 + self.eta_usec_ = 0 + + def has_eta_usec(self): return self.has_eta_usec_ + + def url(self): return self.url_ + + def set_url(self, x): + self.has_url_ = 1 + self.url_ = x + + def clear_url(self): + if self.has_url_: + self.has_url_ = 0 + self.url_ = "" + + def has_url(self): return self.has_url_ + + def method(self): return self.method_ + + def set_method(self, x): + self.has_method_ = 1 + self.method_ = x + + def clear_method(self): + if self.has_method_: + self.has_method_ = 0 + self.method_ = 0 + + def has_method(self): return self.has_method_ + + def retry_count(self): return self.retry_count_ + + def set_retry_count(self, x): + self.has_retry_count_ = 1 + self.retry_count_ = x + + def clear_retry_count(self): + if self.has_retry_count_: + self.has_retry_count_ = 0 + self.retry_count_ = 0 + + def has_retry_count(self): return self.has_retry_count_ + + def header_size(self): return len(self.header_) + def header_list(self): return self.header_ + + def header(self, i): + return self.header_[i] + + def mutable_header(self, i): + return self.header_[i] + + def add_header(self): + x = TaskQueueQueryTasksResponse_TaskHeader() + self.header_.append(x) + return x + + def clear_header(self): + self.header_ = [] + def body_size(self): return self.body_size_ + + def set_body_size(self, x): + self.has_body_size_ = 1 + self.body_size_ = x + + def clear_body_size(self): + if self.has_body_size_: + self.has_body_size_ = 0 + self.body_size_ = 0 + + def has_body_size(self): return self.has_body_size_ + + def body(self): return self.body_ + + def set_body(self, x): + self.has_body_ = 1 + self.body_ = x + + def clear_body(self): + if self.has_body_: + self.has_body_ = 0 + self.body_ = "" + + def has_body(self): return self.has_body_ + + def creation_time_usec(self): return self.creation_time_usec_ + + def set_creation_time_usec(self, x): + self.has_creation_time_usec_ = 1 + self.creation_time_usec_ = x + + def clear_creation_time_usec(self): + if self.has_creation_time_usec_: + self.has_creation_time_usec_ = 0 + self.creation_time_usec_ = 0 + + def has_creation_time_usec(self): return self.has_creation_time_usec_ + + def crontimetable(self): + if self.crontimetable_ is None: + self.lazy_init_lock_.acquire() + try: + if self.crontimetable_ is None: self.crontimetable_ = TaskQueueQueryTasksResponse_TaskCronTimetable() + finally: + self.lazy_init_lock_.release() + return self.crontimetable_ + + def mutable_crontimetable(self): self.has_crontimetable_ = 1; return self.crontimetable() + + def clear_crontimetable(self): + if self.has_crontimetable_: + self.has_crontimetable_ = 0; + if self.crontimetable_ is not None: self.crontimetable_.Clear() + + def has_crontimetable(self): return self.has_crontimetable_ + + def runlog(self): + if self.runlog_ is None: + self.lazy_init_lock_.acquire() + try: + if self.runlog_ is None: self.runlog_ = TaskQueueQueryTasksResponse_TaskRunLog() + finally: + self.lazy_init_lock_.release() + return self.runlog_ + + def mutable_runlog(self): self.has_runlog_ = 1; return self.runlog() + + def clear_runlog(self): + if self.has_runlog_: + self.has_runlog_ = 0; + if self.runlog_ is not None: self.runlog_.Clear() + + def has_runlog(self): return self.has_runlog_ + + def description(self): return self.description_ + + def set_description(self, x): + self.has_description_ = 1 + self.description_ = x + + def clear_description(self): + if self.has_description_: + self.has_description_ = 0 + self.description_ = "" + + def has_description(self): return self.has_description_ + + def payload(self): + if self.payload_ is None: + self.lazy_init_lock_.acquire() + try: + if self.payload_ is None: self.payload_ = MessageSet() + finally: + self.lazy_init_lock_.release() + return self.payload_ + + def mutable_payload(self): self.has_payload_ = 1; return self.payload() + + def clear_payload(self): + if self.has_payload_: + self.has_payload_ = 0; + if self.payload_ is not None: self.payload_.Clear() + + def has_payload(self): return self.has_payload_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_task_name()): self.set_task_name(x.task_name()) + if (x.has_eta_usec()): self.set_eta_usec(x.eta_usec()) + if (x.has_url()): self.set_url(x.url()) + if (x.has_method()): self.set_method(x.method()) + if (x.has_retry_count()): self.set_retry_count(x.retry_count()) + for i in xrange(x.header_size()): self.add_header().CopyFrom(x.header(i)) + if (x.has_body_size()): self.set_body_size(x.body_size()) + if (x.has_body()): self.set_body(x.body()) + if (x.has_creation_time_usec()): self.set_creation_time_usec(x.creation_time_usec()) + if (x.has_crontimetable()): self.mutable_crontimetable().MergeFrom(x.crontimetable()) + if (x.has_runlog()): self.mutable_runlog().MergeFrom(x.runlog()) + if (x.has_description()): self.set_description(x.description()) + if (x.has_payload()): self.mutable_payload().MergeFrom(x.payload()) + + def Equals(self, x): + if x is self: return 1 + if self.has_task_name_ != x.has_task_name_: return 0 + if self.has_task_name_ and self.task_name_ != x.task_name_: return 0 + if self.has_eta_usec_ != x.has_eta_usec_: return 0 + if self.has_eta_usec_ and self.eta_usec_ != x.eta_usec_: return 0 + if self.has_url_ != x.has_url_: return 0 + if self.has_url_ and self.url_ != x.url_: return 0 + if self.has_method_ != x.has_method_: return 0 + if self.has_method_ and self.method_ != x.method_: return 0 + if self.has_retry_count_ != x.has_retry_count_: return 0 + if self.has_retry_count_ and self.retry_count_ != x.retry_count_: return 0 + if len(self.header_) != len(x.header_): return 0 + for e1, e2 in zip(self.header_, x.header_): + if e1 != e2: return 0 + if self.has_body_size_ != x.has_body_size_: return 0 + if self.has_body_size_ and self.body_size_ != x.body_size_: return 0 + if self.has_body_ != x.has_body_: return 0 + if self.has_body_ and self.body_ != x.body_: return 0 + if self.has_creation_time_usec_ != x.has_creation_time_usec_: return 0 + if self.has_creation_time_usec_ and self.creation_time_usec_ != x.creation_time_usec_: return 0 + if self.has_crontimetable_ != x.has_crontimetable_: return 0 + if self.has_crontimetable_ and self.crontimetable_ != x.crontimetable_: return 0 + if self.has_runlog_ != x.has_runlog_: return 0 + if self.has_runlog_ and self.runlog_ != x.runlog_: return 0 + if self.has_description_ != x.has_description_: return 0 + if self.has_description_ and self.description_ != x.description_: return 0 + if self.has_payload_ != x.has_payload_: return 0 + if self.has_payload_ and self.payload_ != x.payload_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_task_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: task_name not set.') + if (not self.has_eta_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: eta_usec not set.') + if (not self.has_method_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: method not set.') + for p in self.header_: + if not p.IsInitialized(debug_strs): initialized=0 + if (not self.has_creation_time_usec_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: creation_time_usec not set.') + if (self.has_crontimetable_ and not self.crontimetable_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_runlog_ and not self.runlog_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_payload_ and not self.payload_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.task_name_)) + n += self.lengthVarInt64(self.eta_usec_) + if (self.has_url_): n += 1 + self.lengthString(len(self.url_)) + n += self.lengthVarInt64(self.method_) + if (self.has_retry_count_): n += 1 + self.lengthVarInt64(self.retry_count_) + n += 2 * len(self.header_) + for i in xrange(len(self.header_)): n += self.header_[i].ByteSize() + if (self.has_body_size_): n += 1 + self.lengthVarInt64(self.body_size_) + if (self.has_body_): n += 1 + self.lengthString(len(self.body_)) + n += self.lengthVarInt64(self.creation_time_usec_) + if (self.has_crontimetable_): n += 2 + self.crontimetable_.ByteSize() + if (self.has_runlog_): n += 4 + self.runlog_.ByteSize() + if (self.has_description_): n += 2 + self.lengthString(len(self.description_)) + if (self.has_payload_): n += 2 + self.lengthString(self.payload_.ByteSize()) + return n + 4 + + def Clear(self): + self.clear_task_name() + self.clear_eta_usec() + self.clear_url() + self.clear_method() + self.clear_retry_count() + self.clear_header() + self.clear_body_size() + self.clear_body() + self.clear_creation_time_usec() + self.clear_crontimetable() + self.clear_runlog() + self.clear_description() + self.clear_payload() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.task_name_) + out.putVarInt32(24) + out.putVarInt64(self.eta_usec_) + if (self.has_url_): + out.putVarInt32(34) + out.putPrefixedString(self.url_) + out.putVarInt32(40) + out.putVarInt32(self.method_) + if (self.has_retry_count_): + out.putVarInt32(48) + out.putVarInt32(self.retry_count_) + for i in xrange(len(self.header_)): + out.putVarInt32(59) + self.header_[i].OutputUnchecked(out) + out.putVarInt32(60) + if (self.has_body_size_): + out.putVarInt32(80) + out.putVarInt32(self.body_size_) + if (self.has_body_): + out.putVarInt32(90) + out.putPrefixedString(self.body_) + out.putVarInt32(96) + out.putVarInt64(self.creation_time_usec_) + if (self.has_crontimetable_): + out.putVarInt32(107) + self.crontimetable_.OutputUnchecked(out) + out.putVarInt32(108) + if (self.has_runlog_): + out.putVarInt32(131) + self.runlog_.OutputUnchecked(out) + out.putVarInt32(132) + if (self.has_description_): + out.putVarInt32(170) + out.putPrefixedString(self.description_) + if (self.has_payload_): + out.putVarInt32(178) + out.putVarInt32(self.payload_.ByteSize()) + self.payload_.OutputUnchecked(out) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_task_name(d.getPrefixedString()) + continue + if tt == 24: + self.set_eta_usec(d.getVarInt64()) + continue + if tt == 34: + self.set_url(d.getPrefixedString()) + continue + if tt == 40: + self.set_method(d.getVarInt32()) + continue + if tt == 48: + self.set_retry_count(d.getVarInt32()) + continue + if tt == 59: + self.add_header().TryMerge(d) + continue + if tt == 80: + self.set_body_size(d.getVarInt32()) + continue + if tt == 90: + self.set_body(d.getPrefixedString()) + continue + if tt == 96: + self.set_creation_time_usec(d.getVarInt64()) + continue + if tt == 107: + self.mutable_crontimetable().TryMerge(d) + continue + if tt == 131: + self.mutable_runlog().TryMerge(d) + continue + if tt == 170: + self.set_description(d.getPrefixedString()) + continue + if tt == 178: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_payload().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_task_name_: res+=prefix+("task_name: %s\n" % self.DebugFormatString(self.task_name_)) + if self.has_eta_usec_: res+=prefix+("eta_usec: %s\n" % self.DebugFormatInt64(self.eta_usec_)) + if self.has_url_: res+=prefix+("url: %s\n" % self.DebugFormatString(self.url_)) + if self.has_method_: res+=prefix+("method: %s\n" % self.DebugFormatInt32(self.method_)) + if self.has_retry_count_: res+=prefix+("retry_count: %s\n" % self.DebugFormatInt32(self.retry_count_)) + cnt=0 + for e in self.header_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Header%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_body_size_: res+=prefix+("body_size: %s\n" % self.DebugFormatInt32(self.body_size_)) + if self.has_body_: res+=prefix+("body: %s\n" % self.DebugFormatString(self.body_)) + if self.has_creation_time_usec_: res+=prefix+("creation_time_usec: %s\n" % self.DebugFormatInt64(self.creation_time_usec_)) + if self.has_crontimetable_: + res+=prefix+"CronTimetable {\n" + res+=self.crontimetable_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + if self.has_runlog_: + res+=prefix+"RunLog {\n" + res+=self.runlog_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + if self.has_description_: res+=prefix+("description: %s\n" % self.DebugFormatString(self.description_)) + if self.has_payload_: + res+=prefix+"payload <\n" + res+=self.payload_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + +class TaskQueueQueryTasksResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.task_ = [] + if contents is not None: self.MergeFromString(contents) + + def task_size(self): return len(self.task_) + def task_list(self): return self.task_ + + def task(self, i): + return self.task_[i] + + def mutable_task(self, i): + return self.task_[i] + + def add_task(self): + x = TaskQueueQueryTasksResponse_Task() + self.task_.append(x) + return x + + def clear_task(self): + self.task_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.task_size()): self.add_task().CopyFrom(x.task(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.task_) != len(x.task_): return 0 + for e1, e2 in zip(self.task_, x.task_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.task_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.task_) + for i in xrange(len(self.task_)): n += self.task_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_task() + + def OutputUnchecked(self, out): + for i in xrange(len(self.task_)): + out.putVarInt32(11) + self.task_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_task().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.task_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Task%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kTaskGroup = 1 + kTasktask_name = 2 + kTasketa_usec = 3 + kTaskurl = 4 + kTaskmethod = 5 + kTaskretry_count = 6 + kTaskHeaderGroup = 7 + kTaskHeaderkey = 8 + kTaskHeadervalue = 9 + kTaskbody_size = 10 + kTaskbody = 11 + kTaskcreation_time_usec = 12 + kTaskCronTimetableGroup = 13 + kTaskCronTimetableschedule = 14 + kTaskCronTimetabletimezone = 15 + kTaskRunLogGroup = 16 + kTaskRunLogdispatched_usec = 17 + kTaskRunLoglag_usec = 18 + kTaskRunLogelapsed_usec = 19 + kTaskRunLogresponse_code = 20 + kTaskdescription = 21 + kTaskpayload = 22 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Task", + 2: "task_name", + 3: "eta_usec", + 4: "url", + 5: "method", + 6: "retry_count", + 7: "Header", + 8: "key", + 9: "value", + 10: "body_size", + 11: "body", + 12: "creation_time_usec", + 13: "CronTimetable", + 14: "schedule", + 15: "timezone", + 16: "RunLog", + 17: "dispatched_usec", + 18: "lag_usec", + 19: "elapsed_usec", + 20: "response_code", + 21: "description", + 22: "payload", + }, 22) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.NUMERIC, + 7: ProtocolBuffer.Encoder.STARTGROUP, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.STRING, + 10: ProtocolBuffer.Encoder.NUMERIC, + 11: ProtocolBuffer.Encoder.STRING, + 12: ProtocolBuffer.Encoder.NUMERIC, + 13: ProtocolBuffer.Encoder.STARTGROUP, + 14: ProtocolBuffer.Encoder.STRING, + 15: ProtocolBuffer.Encoder.STRING, + 16: ProtocolBuffer.Encoder.STARTGROUP, + 17: ProtocolBuffer.Encoder.NUMERIC, + 18: ProtocolBuffer.Encoder.NUMERIC, + 19: ProtocolBuffer.Encoder.NUMERIC, + 20: ProtocolBuffer.Encoder.NUMERIC, + 21: ProtocolBuffer.Encoder.STRING, + 22: ProtocolBuffer.Encoder.STRING, + }, 22, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueUpdateStorageLimitRequest(ProtocolBuffer.ProtocolMessage): + has_app_id_ = 0 + app_id_ = "" + has_limit_ = 0 + limit_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def limit(self): return self.limit_ + + def set_limit(self, x): + self.has_limit_ = 1 + self.limit_ = x + + def clear_limit(self): + if self.has_limit_: + self.has_limit_ = 0 + self.limit_ = 0 + + def has_limit(self): return self.has_limit_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_limit()): self.set_limit(x.limit()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_limit_ != x.has_limit_: return 0 + if self.has_limit_ and self.limit_ != x.limit_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_limit_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: limit not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthVarInt64(self.limit_) + return n + 2 + + def Clear(self): + self.clear_app_id() + self.clear_limit() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(16) + out.putVarInt64(self.limit_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 16: + self.set_limit(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_limit_: res+=prefix+("limit: %s\n" % self.DebugFormatInt64(self.limit_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + klimit = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "limit", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TaskQueueUpdateStorageLimitResponse(ProtocolBuffer.ProtocolMessage): + has_new_limit_ = 0 + new_limit_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def new_limit(self): return self.new_limit_ + + def set_new_limit(self, x): + self.has_new_limit_ = 1 + self.new_limit_ = x + + def clear_new_limit(self): + if self.has_new_limit_: + self.has_new_limit_ = 0 + self.new_limit_ = 0 + + def has_new_limit(self): return self.has_new_limit_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_new_limit()): self.set_new_limit(x.new_limit()) + + def Equals(self, x): + if x is self: return 1 + if self.has_new_limit_ != x.has_new_limit_: return 0 + if self.has_new_limit_ and self.new_limit_ != x.new_limit_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_new_limit_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: new_limit not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.new_limit_) + return n + 1 + + def Clear(self): + self.clear_new_limit() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.new_limit_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_new_limit(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_new_limit_: res+=prefix+("new_limit: %s\n" % self.DebugFormatInt64(self.new_limit_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + knew_limit = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "new_limit", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['TaskQueueServiceError','TaskQueueAddRequest','TaskQueueAddRequest_Header','TaskQueueAddRequest_CronTimetable','TaskQueueAddResponse','TaskQueueBulkAddRequest','TaskQueueBulkAddResponse','TaskQueueBulkAddResponse_TaskResult','TaskQueueDeleteRequest','TaskQueueDeleteResponse','TaskQueueUpdateQueueRequest','TaskQueueUpdateQueueResponse','TaskQueueFetchQueuesRequest','TaskQueueFetchQueuesResponse','TaskQueueFetchQueuesResponse_Queue','TaskQueueFetchQueueStatsRequest','TaskQueueScannerQueueInfo','TaskQueueFetchQueueStatsResponse','TaskQueueFetchQueueStatsResponse_QueueStats','TaskQueuePauseQueueRequest','TaskQueuePauseQueueResponse','TaskQueuePurgeQueueRequest','TaskQueuePurgeQueueResponse','TaskQueueDeleteQueueRequest','TaskQueueDeleteQueueResponse','TaskQueueQueryTasksRequest','TaskQueueQueryTasksResponse','TaskQueueQueryTasksResponse_TaskHeader','TaskQueueQueryTasksResponse_TaskCronTimetable','TaskQueueQueryTasksResponse_TaskRunLog','TaskQueueQueryTasksResponse_Task','TaskQueueUpdateStorageLimitRequest','TaskQueueUpdateStorageLimitResponse'] diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.pyc b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95312a586f4675e16b2f00e33ed5851db99a2213 GIT binary patch literal 224786 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py new file mode 100755 index 0000000..94aad51 --- /dev/null +++ b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.py @@ -0,0 +1,956 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the Task Queue API. + +This stub stores tasks and runs them via dev_appserver's AddEvent capability. +It also validates the tasks by checking their queue name against the queue.yaml. + +As well as implementing Task Queue API functions, the stub exposes various other +functions that are used by the dev_appserver's admin console to display the +application's queues and tasks. +""" + + + + +import StringIO +import base64 +import bisect +import datetime +import logging +import os +import random +import string +import time + +import taskqueue_service_pb + +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_stub +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import queueinfo +from google.appengine.runtime import apiproxy_errors + + +DEFAULT_RATE = '5.00/s' + +DEFAULT_BUCKET_SIZE = 5 + +MAX_ETA_DELTA_DAYS = 30 + +admin_console_dummy_tasks = {} + +BUILT_IN_HEADERS = set(['x-appengine-queuename', + 'x-appengine-taskname', + 'x-appengine-taskretrycount', + 'x-appengine-development-payload', + 'content-length']) + +DEFAULT_QUEUE_NAME = 'default' + +CRON_QUEUE_NAME = '__cron' + + +class _DummyTaskStore(object): + """A class that encapsulates a sorted store of tasks. + + Used for testing the admin console. + """ + + def __init__(self): + """Constructor.""" + self._sorted_by_name = [] + self._sorted_by_eta = [] + + def _InsertTask(self, task): + """Insert a task into the dummy store, keeps lists sorted. + + Args: + task: the new task. + """ + eta = task.eta_usec() + name = task.task_name() + bisect.insort_left(self._sorted_by_eta, (eta, name, task)) + bisect.insort_left(self._sorted_by_name, (name, task)) + + def Lookup(self, maximum, name=None, eta=None): + """Lookup a number of sorted tasks from the store. + + If 'eta' is specified, the tasks are looked up in a list sorted by 'eta', + then 'name'. Otherwise they are sorted by 'name'. We need to be able to + sort by 'eta' and 'name' because tasks can have identical eta. If you had + 20 tasks with the same ETA, you wouldn't be able to page past them, since + the 'next eta' would give the first one again. Names are unique, though. + + Args: + maximum: the maximum number of tasks to return. + name: a task name to start with. + eta: an eta to start with. + + Returns: + A list of up to 'maximum' tasks. + + Raises: + ValueError: if the task store gets corrupted. + """ + if eta is None: + pos = bisect.bisect_left(self._sorted_by_name, (name,)) + tasks = (x[1] for x in self._sorted_by_name[pos:pos + maximum]) + return list(tasks) + if name is None: + raise ValueError('must supply name or eta') + pos = bisect.bisect_left(self._sorted_by_eta, (eta, name)) + tasks = (x[2] for x in self._sorted_by_eta[pos:pos + maximum]) + return list(tasks) + + def Count(self): + """Returns the number of tasks in the store.""" + return len(self._sorted_by_name) + + def Oldest(self): + """Returns the oldest eta in the store, or None if no tasks.""" + if self._sorted_by_eta: + return self._sorted_by_eta[0][0] + return None + + def Add(self, request): + """Inserts a new task into the store. + + Args: + request: A taskqueue_service_pb.TaskQueueAddRequest. + + Raises: + apiproxy_errors.ApplicationError: If a task with the same name is already + in the store. + """ + pos = bisect.bisect_left(self._sorted_by_name, (request.task_name(),)) + if (pos < len(self._sorted_by_name) and + self._sorted_by_name[pos][0] == request.task_name()): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.TASK_ALREADY_EXISTS) + + now = datetime.datetime.utcnow() + now_sec = time.mktime(now.timetuple()) + task = taskqueue_service_pb.TaskQueueQueryTasksResponse_Task() + task.set_task_name(request.task_name()) + task.set_eta_usec(request.eta_usec()) + task.set_creation_time_usec(now_sec * 1e6) + task.set_url(request.url()) + task.set_method(request.method()) + for keyvalue in task.header_list(): + header = task.add_header() + header.set_key(keyvalue.key()) + header.set_value(keyvalue.value()) + if request.has_description(): + task.set_description(request.description()) + if request.has_body(): + task.set_body(request.body()) + if request.has_crontimetable(): + task.mutable_crontimetable().set_schedule( + request.crontimetable().schedule()) + task.mutable_crontimetable().set_timezone( + request.crontimetable().timezone()) + self._InsertTask(task) + + def Delete(self, name): + """Deletes a task from the store by name. + + Args: + name: the name of the task to delete. + + Returns: + TaskQueueServiceError.UNKNOWN_TASK: if the task is unknown. + TaskQueueServiceError.INTERNAL_ERROR: if the store is corrupted. + TaskQueueServiceError.OK: otherwise. + """ + pos = bisect.bisect_left(self._sorted_by_name, (name,)) + if pos >= len(self._sorted_by_name): + return taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_TASK + if self._sorted_by_name[pos][1].task_name() != name: + logging.info('looking for task name %s, got task name %s', name, + self._sorted_by_name[pos][1].task_name()) + return taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_TASK + old_task = self._sorted_by_name.pop(pos)[1] + eta = old_task.eta_usec() + pos = bisect.bisect_left(self._sorted_by_eta, (eta, name, None)) + if self._sorted_by_eta[pos][2] is not old_task: + logging.error('task store corrupted') + return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERRROR + self._sorted_by_eta.pop(pos) + return taskqueue_service_pb.TaskQueueServiceError.OK + + def Populate(self, num_tasks): + """Populates the store with a number of tasks. + + Args: + num_tasks: the number of tasks to insert. + """ + now = datetime.datetime.utcnow() + now_sec = time.mktime(now.timetuple()) + + def RandomTask(): + """Creates a new task and randomly populates values.""" + task = taskqueue_service_pb.TaskQueueQueryTasksResponse_Task() + task.set_task_name(''.join(random.choice(string.ascii_lowercase) + for x in range(20))) + task.set_eta_usec(int(now_sec * 1e6) + random.randint(-10e6, 600e6)) + + task.set_creation_time_usec(min(now_sec * 1e6, task.eta_usec()) - + random.randint(0, 2e7)) + + task.set_url(random.choice(['/a', '/b', '/c', '/d'])) + if random.random() < 0.2: + task.set_method( + taskqueue_service_pb.TaskQueueQueryTasksResponse_Task.POST) + task.set_body('A' * 2000) + else: + task.set_method( + taskqueue_service_pb.TaskQueueQueryTasksResponse_Task.GET) + task.set_retry_count(max(0, random.randint(-10, 5))) + if random.random() < 0.3: + random_headers = [('nexus', 'one'), + ('foo', 'bar'), + ('content-type', 'text/plain'), + ('from', 'user@email.com')] + for _ in xrange(random.randint(1, 4)): + elem = random.randint(0, len(random_headers)-1) + key, value = random_headers.pop(elem) + header_proto = task.add_header() + header_proto.set_key(key) + header_proto.set_value(value) + return task + + for _ in range(num_tasks): + self._InsertTask(RandomTask()) + + +def _ParseQueueYaml(unused_self, root_path): + """Loads the queue.yaml file and parses it. + + Args: + unused_self: Allows this function to be bound to a class member. Not used. + root_path: Directory containing queue.yaml. Not used. + + Returns: + None if queue.yaml doesn't exist, otherwise a queueinfo.QueueEntry object + populated from the queue.yaml. + """ + if root_path is None: + return None + for queueyaml in ('queue.yaml', 'queue.yml'): + try: + fh = open(os.path.join(root_path, queueyaml), 'r') + except IOError: + continue + try: + queue_info = queueinfo.LoadSingleQueue(fh) + return queue_info + finally: + fh.close() + return None + + +def _CompareTasksByEta(a, b): + """Python sort comparator for tasks by estimated time of arrival (ETA). + + Args: + a: A taskqueue_service_pb.TaskQueueAddRequest. + b: A taskqueue_service_pb.TaskQueueAddRequest. + + Returns: + Standard 1/0/-1 comparison result. + """ + if a.eta_usec() > b.eta_usec(): + return 1 + if a.eta_usec() < b.eta_usec(): + return -1 + return 0 + + +def _FormatEta(eta_usec): + """Formats a task ETA as a date string in UTC.""" + eta = datetime.datetime.fromtimestamp(eta_usec/1000000) + return eta.strftime('%Y/%m/%d %H:%M:%S') + + +def _EtaDelta(eta_usec): + """Formats a task ETA as a relative time string.""" + eta = datetime.datetime.fromtimestamp(eta_usec/1000000) + now = datetime.datetime.utcnow() + if eta > now: + return str(eta - now) + ' from now' + else: + return str(now - eta) + ' ago' + + +class TaskQueueServiceStub(apiproxy_stub.APIProxyStub): + """Python only task queue service stub. + + This stub executes tasks when enabled by using the dev_appserver's AddEvent + capability. When task running is disabled this stub will store tasks for + display on a console, where the user may manually execute the tasks. + """ + + queue_yaml_parser = _ParseQueueYaml + + def __init__(self, + service_name='taskqueue', + root_path=None, + auto_task_running=False, + task_retry_seconds=30, + _all_queues_valid=False): + """Constructor. + + Args: + service_name: Service name expected for all calls. + root_path: Root path to the directory of the application which may contain + a queue.yaml file. If None, then it's assumed no queue.yaml file is + available. + auto_task_running: When True, the dev_appserver should automatically + run tasks after they are enqueued. + task_retry_seconds: How long to wait between task executions after a + task fails. + """ + super(TaskQueueServiceStub, self).__init__(service_name) + self._taskqueues = {} + self._next_task_id = 1 + self._root_path = root_path + self._all_queues_valid = _all_queues_valid + + self._add_event = None + self._auto_task_running = auto_task_running + self._task_retry_seconds = task_retry_seconds + + self._app_queues = {} + + class _QueueDetails(taskqueue_service_pb.TaskQueueUpdateQueueRequest): + def __init__(self, paused=False): + self.paused = paused + + def _ChooseTaskName(self): + """Returns a string containing a unique task name.""" + self._next_task_id += 1 + return 'task%d' % (self._next_task_id - 1) + + def _VerifyTaskQueueAddRequest(self, request): + """Checks that a TaskQueueAddRequest is valid. + + Checks that a TaskQueueAddRequest specifies a valid eta and a valid queue. + + Args: + request: The taskqueue_service_pb.TaskQueueAddRequest to validate. + + Returns: + A taskqueue_service_pb.TaskQueueServiceError indicating any problems with + the request or taskqueue_service_pb.TaskQueueServiceError.OK if it is + valid. + """ + if request.eta_usec() < 0: + return taskqueue_service_pb.TaskQueueServiceError.INVALID_ETA + + eta = datetime.datetime.utcfromtimestamp(request.eta_usec() / 1e6) + max_eta = (datetime.datetime.utcnow() + + datetime.timedelta(days=MAX_ETA_DELTA_DAYS)) + if eta > max_eta: + return taskqueue_service_pb.TaskQueueServiceError.INVALID_ETA + + return taskqueue_service_pb.TaskQueueServiceError.OK + + def _Dynamic_Add(self, request, response): + bulk_request = taskqueue_service_pb.TaskQueueBulkAddRequest() + bulk_response = taskqueue_service_pb.TaskQueueBulkAddResponse() + + bulk_request.add_add_request().CopyFrom(request) + self._Dynamic_BulkAdd(bulk_request, bulk_response) + + assert bulk_response.taskresult_size() == 1 + result = bulk_response.taskresult(0).result() + + if result != taskqueue_service_pb.TaskQueueServiceError.OK: + raise apiproxy_errors.ApplicationError(result) + elif bulk_response.taskresult(0).has_chosen_task_name(): + response.set_chosen_task_name( + bulk_response.taskresult(0).chosen_task_name()) + + def _Dynamic_BulkAdd(self, request, response): + """Add many tasks to a queue using a single request. + + Args: + request: The taskqueue_service_pb.TaskQueueBulkAddRequest. See + taskqueue_service.proto. + response: The taskqueue_service_pb.TaskQueueBulkAddResponse. See + taskqueue_service.proto. + """ + + assert request.add_request_size(), 'taskqueue should prevent empty requests' + + if not self._IsValidQueue(request.add_request(0).queue_name()): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE) + + error_found = False + task_results_with_chosen_names = [] + + for add_request in request.add_request_list(): + task_result = response.add_taskresult() + error = self._VerifyTaskQueueAddRequest(add_request) + if error == taskqueue_service_pb.TaskQueueServiceError.OK: + if not add_request.task_name(): + chosen_name = self._ChooseTaskName() + add_request.set_task_name(chosen_name) + task_results_with_chosen_names.append(task_result) + task_result.set_result( + taskqueue_service_pb.TaskQueueServiceError.SKIPPED) + else: + error_found = True + task_result.set_result(error) + + if error_found: + return + + if request.add_request(0).has_transaction(): + self._TransactionalBulkAdd(request) + elif request.add_request(0).has_app_id(): + self._DummyTaskStoreBulkAdd(request, response) + else: + self._NonTransactionalBulkAdd(request, response) + + for add_request, task_result in zip(request.add_request_list(), + response.taskresult_list()): + if (task_result.result() == + taskqueue_service_pb.TaskQueueServiceError.SKIPPED): + task_result.set_result(taskqueue_service_pb.TaskQueueServiceError.OK) + if task_result in task_results_with_chosen_names: + task_result.set_chosen_task_name(add_request.task_name()) + + def _TransactionalBulkAdd(self, request): + """Uses datastore.AddActions to associate tasks with a transaction. + + Args: + request: The taskqueue_service_pb.TaskQueueBulkAddRequest containing the + tasks to add. N.B. all tasks in the request have been validated and + assigned unique names. + """ + try: + apiproxy_stub_map.MakeSyncCall( + 'datastore_v3', 'AddActions', request, api_base_pb.VoidProto()) + except apiproxy_errors.ApplicationError, e: + raise apiproxy_errors.ApplicationError( + e.application_error + + taskqueue_service_pb.TaskQueueServiceError.DATASTORE_ERROR, + e.error_detail) + + def _DummyTaskStoreBulkAdd(self, request, response): + """Adds tasks to the appropriate DummyTaskStore. + + Args: + request: The taskqueue_service_pb.TaskQueueBulkAddRequest containing the + tasks to add. N.B. all tasks in the request have been validated and + those with empty names have been assigned unique names. + response: The taskqueue_service_pb.TaskQueueBulkAddResponse to populate + with the results. N.B. the chosen_task_name field in the response will + not be filled-in. + """ + store = self.GetDummyTaskStore(request.add_request(0).app_id(), + request.add_request(0).queue_name()) + for add_request, task_result in zip(request.add_request_list(), + response.taskresult_list()): + try: + store.Add(add_request) + except apiproxy_errors.ApplicationError, e: + task_result.set_result(e.application_error) + else: + task_result.set_result(taskqueue_service_pb.TaskQueueServiceError.OK) + + def _NonTransactionalBulkAdd(self, request, response): + """Adds tasks to the appropriate list in in self._taskqueues. + + Args: + request: The taskqueue_service_pb.TaskQueueBulkAddRequest containing the + tasks to add. N.B. all tasks in the request have been validated and + those with empty names have been assigned unique names. + response: The taskqueue_service_pb.TaskQueueBulkAddResponse to populate + with the results. N.B. the chosen_task_name field in the response will + not be filled-in. + """ + existing_tasks = self._taskqueues.setdefault( + request.add_request(0).queue_name(), []) + existing_task_names = set(task.task_name() for task in existing_tasks) + + for add_request, task_result in zip(request.add_request_list(), + response.taskresult_list()): + if add_request.task_name() in existing_task_names: + task_result.set_result( + taskqueue_service_pb.TaskQueueServiceError.TASK_ALREADY_EXISTS) + else: + existing_tasks.append(add_request) + + if self._add_event and self._auto_task_running: + self._add_event( + add_request.eta_usec() / 1000000.0, + lambda: self._RunTask( + add_request.queue_name(), add_request.task_name())) + + existing_tasks.sort(_CompareTasksByEta) + + def _IsValidQueue(self, queue_name): + """Determines whether a queue is valid, i.e. tasks can be added to it. + + Valid queues are the 'default' queue, plus any queues in the queue.yaml + file. + + Args: + queue_name: the name of the queue to validate. + + Returns: + True iff queue is valid. + """ + if self._all_queues_valid: + return True + if queue_name == DEFAULT_QUEUE_NAME or queue_name == CRON_QUEUE_NAME: + return True + queue_info = self.queue_yaml_parser(self._root_path) + if queue_info and queue_info.queue: + for entry in queue_info.queue: + if entry.name == queue_name: + return True + return False + + def _RunTask(self, queue_name, task_name): + """Returns a fake request for running a task in the dev_appserver. + + Args: + queue_name: The queue the task is in. + task_name: The name of the task to run. + + Returns: + None if this task no longer exists or tuple (connection, addrinfo) of + a fake connection and address information used to run this task. The + task will be deleted after it runs or re-enqueued in the future on + failure. + """ + task_list = self.GetTasks(queue_name) + for task in task_list: + if task['name'] == task_name: + break + else: + return None + + class FakeConnection(object): + def __init__(self, input_buffer): + self.rfile = StringIO.StringIO(input_buffer) + self.wfile = StringIO.StringIO() + self.wfile_close = self.wfile.close + self.wfile.close = self.connection_done + + def connection_done(myself): + result = myself.wfile.getvalue() + myself.wfile_close() + first_line, rest = (result.split('\n', 1) + ['', ''])[:2] + version, code, rest = (first_line.split(' ', 2) + ['', '500', ''])[:3] + if 200 <= int(code) <= 299: + self.DeleteTask(queue_name, task_name) + return + + logging.warning('Task named "%s" on queue "%s" failed with code %s; ' + 'will retry in %d seconds', + task_name, queue_name, code, self._task_retry_seconds) + self._add_event( + time.time() + self._task_retry_seconds, + lambda: self._RunTask(queue_name, task_name)) + + def close(self): + pass + + def makefile(self, mode, buffsize): + if mode.startswith('w'): + return self.wfile + else: + return self.rfile + + payload = StringIO.StringIO() + payload.write('%s %s HTTP/1.1\r\n' % (task['method'], task['url'])) + for key, value in task['headers']: + payload.write('%s: %s\r\n' % (key, value)) + payload.write('\r\n') + payload.write(task['body']) + + return FakeConnection(payload.getvalue()), ('0.1.0.2', 80) + + def GetQueues(self): + """Gets all the applications's queues. + + Returns: + A list of dictionaries, where each dictionary contains one queue's + attributes. E.g.: + [{'name': 'some-queue', + 'max_rate': '1/s', + 'bucket_size': 5, + 'oldest_task': '2009/02/02 05:37:42', + 'eta_delta': '0:00:06.342511 ago', + 'tasks_in_queue': 12}, ...] + The list of queues always includes the default queue. + """ + queues = [] + queue_info = self.queue_yaml_parser(self._root_path) + has_default = False + if queue_info and queue_info.queue: + for entry in queue_info.queue: + if entry.name == DEFAULT_QUEUE_NAME: + has_default = True + queue = {} + queues.append(queue) + queue['name'] = entry.name + queue['max_rate'] = entry.rate + if entry.bucket_size: + queue['bucket_size'] = entry.bucket_size + else: + queue['bucket_size'] = DEFAULT_BUCKET_SIZE + + tasks = self._taskqueues.setdefault(entry.name, []) + if tasks: + queue['oldest_task'] = _FormatEta(tasks[0].eta_usec()) + queue['eta_delta'] = _EtaDelta(tasks[0].eta_usec()) + else: + queue['oldest_task'] = '' + queue['tasks_in_queue'] = len(tasks) + + if not has_default: + queue = {} + queues.append(queue) + queue['name'] = DEFAULT_QUEUE_NAME + queue['max_rate'] = DEFAULT_RATE + queue['bucket_size'] = DEFAULT_BUCKET_SIZE + + tasks = self._taskqueues.get(DEFAULT_QUEUE_NAME, []) + if tasks: + queue['oldest_task'] = _FormatEta(tasks[0].eta_usec()) + queue['eta_delta'] = _EtaDelta(tasks[0].eta_usec()) + else: + queue['oldest_task'] = '' + queue['tasks_in_queue'] = len(tasks) + return queues + + def GetTasks(self, queue_name): + """Gets a queue's tasks. + + Args: + queue_name: Queue's name to return tasks for. + + Returns: + A list of dictionaries, where each dictionary contains one task's + attributes. E.g. + [{'name': 'task-123', + 'url': '/update', + 'method': 'GET', + 'eta': '2009/02/02 05:37:42', + 'eta_delta': '0:00:06.342511 ago', + 'body': '', + 'headers': [('user-header', 'some-value') + ('X-AppEngine-QueueName': 'update-queue'), + ('X-AppEngine-TaskName': 'task-123'), + ('X-AppEngine-TaskRetryCount': '0'), + ('X-AppEngine-Development-Payload': '1'), + ('Content-Length': 0), + ('Content-Type': 'application/octet-stream')] + + Raises: + ValueError: A task request contains an unknown HTTP method type. + """ + tasks = self._taskqueues.get(queue_name, []) + result_tasks = [] + for task_request in tasks: + task = {} + result_tasks.append(task) + task['name'] = task_request.task_name() + task['url'] = task_request.url() + method = task_request.method() + if method == taskqueue_service_pb.TaskQueueAddRequest.GET: + task['method'] = 'GET' + elif method == taskqueue_service_pb.TaskQueueAddRequest.POST: + task['method'] = 'POST' + elif method == taskqueue_service_pb.TaskQueueAddRequest.HEAD: + task['method'] = 'HEAD' + elif method == taskqueue_service_pb.TaskQueueAddRequest.PUT: + task['method'] = 'PUT' + elif method == taskqueue_service_pb.TaskQueueAddRequest.DELETE: + task['method'] = 'DELETE' + else: + raise ValueError('Unexpected method: %d' % method) + + task['eta'] = _FormatEta(task_request.eta_usec()) + task['eta_delta'] = _EtaDelta(task_request.eta_usec()) + task['body'] = base64.b64encode(task_request.body()) + + headers = [(header.key(), header.value()) + for header in task_request.header_list() + if header.key().lower() not in BUILT_IN_HEADERS] + + headers.append(('X-AppEngine-QueueName', queue_name)) + headers.append(('X-AppEngine-TaskName', task['name'])) + headers.append(('X-AppEngine-TaskRetryCount', '0')) + headers.append(('X-AppEngine-Development-Payload', '1')) + headers.append(('Content-Length', len(task['body']))) + if 'content-type' not in frozenset(key.lower() for key, _ in headers): + headers.append(('Content-Type', 'application/octet-stream')) + task['headers'] = headers + + return result_tasks + + def DeleteTask(self, queue_name, task_name): + """Deletes a task from a queue. + + Args: + queue_name: the name of the queue to delete the task from. + task_name: the name of the task to delete. + """ + tasks = self._taskqueues.get(queue_name, []) + for task in tasks: + if task.task_name() == task_name: + tasks.remove(task) + return + + def FlushQueue(self, queue_name): + """Removes all tasks from a queue. + + Args: + queue_name: the name of the queue to remove tasks from. + """ + self._taskqueues[queue_name] = [] + + def _Dynamic_UpdateQueue(self, request, unused_response): + """Local implementation of the UpdateQueue RPC in TaskQueueService. + + Must adhere to the '_Dynamic_' naming convention for stubbing to work. + See taskqueue_service.proto for a full description of the RPC. + + Args: + request: A taskqueue_service_pb.TaskQueueUpdateQueueRequest. + unused_response: A taskqueue_service_pb.TaskQueueUpdateQueueResponse. + Not used. + """ + queues = self._app_queues.setdefault(request.app_id(), {}) + if request.queue_name() in queues and queues[request.queue_name()] is None: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_QUEUE) + + defensive_copy = self._QueueDetails() + defensive_copy.CopyFrom(request) + + queues[request.queue_name()] = defensive_copy + + def _Dynamic_FetchQueues(self, request, response): + """Local implementation of the FetchQueues RPC in TaskQueueService. + + Must adhere to the '_Dynamic_' naming convention for stubbing to work. + See taskqueue_service.proto for a full description of the RPC. + + Args: + request: A taskqueue_service_pb.TaskQueueFetchQueuesRequest. + response: A taskqueue_service_pb.TaskQueueFetchQueuesResponse. + """ + queues = self._app_queues.get(request.app_id(), {}) + for unused_key, queue in sorted(queues.items()): + if request.max_rows() == response.queue_size(): + break + + if queue is None: + continue + + response_queue = response.add_queue() + response_queue.set_queue_name(queue.queue_name()) + response_queue.set_bucket_refill_per_second( + queue.bucket_refill_per_second()) + response_queue.set_bucket_capacity(queue.bucket_capacity()) + response_queue.set_user_specified_rate(queue.user_specified_rate()) + response_queue.set_paused(queue.paused) + + def _Dynamic_FetchQueueStats(self, request, response): + """Local 'random' implementation of the TaskQueueService.FetchQueueStats. + + This implementation loads some stats from the dummy store, + the rest with random numbers. + Must adhere to the '_Dynamic_' naming convention for stubbing to work. + See taskqueue_service.proto for a full description of the RPC. + + Args: + request: A taskqueue_service_pb.TaskQueueFetchQueueStatsRequest. + response: A taskqueue_service_pb.TaskQueueFetchQueueStatsResponse. + """ + for queue in request.queue_name_list(): + store = self.GetDummyTaskStore(request.app_id(), queue) + stats = response.add_queuestats() + stats.set_num_tasks(store.Count()) + if stats.num_tasks() == 0: + stats.set_oldest_eta_usec(-1) + else: + stats.set_oldest_eta_usec(store.Oldest()) + + if random.randint(0, 9) > 0: + scanner_info = stats.mutable_scanner_info() + scanner_info.set_executed_last_minute(random.randint(0, 10)) + scanner_info.set_executed_last_hour(scanner_info.executed_last_minute() + + random.randint(0, 100)) + scanner_info.set_sampling_duration_seconds(random.random() * 10000.0) + + def GetDummyTaskStore(self, app_id, queue_name): + """Get the dummy task store for this app_id/queue_name pair. + + Creates an entry and populates it, if there's not already an entry. + + Args: + app_id: the app_id. + queue_name: the queue_name. + + Returns: + the existing or the new dummy store. + """ + task_store_key = (app_id, queue_name) + if task_store_key not in admin_console_dummy_tasks: + store = _DummyTaskStore() + if not self._all_queues_valid and queue_name != CRON_QUEUE_NAME: + store.Populate(random.randint(10, 100)) + admin_console_dummy_tasks[task_store_key] = store + else: + store = admin_console_dummy_tasks[task_store_key] + return store + + def _Dynamic_QueryTasks(self, request, response): + """Local implementation of the TaskQueueService.QueryTasks RPC. + + Uses the dummy store, creating tasks if this is the first time the + queue has been seen. + + Args: + request: A taskqueue_service_pb.TaskQueueQueryTasksRequest. + response: A taskqueue_service_pb.TaskQueueQueryTasksResponse. + """ + store = self.GetDummyTaskStore(request.app_id(), request.queue_name()) + + if request.has_start_eta_usec(): + tasks = store.Lookup(request.max_rows(), name=request.start_task_name(), + eta=request.start_eta_usec()) + else: + tasks = store.Lookup(request.max_rows(), name=request.start_task_name()) + for task in tasks: + response.add_task().MergeFrom(task) + + def _Dynamic_Delete(self, request, response): + """Local delete implementation of TaskQueueService.Delete. + + Deletes tasks from the dummy store. A 1/20 chance of a transient error. + + Args: + request: A taskqueue_service_pb.TaskQueueDeleteRequest. + response: A taskqueue_service_pb.TaskQueueDeleteResponse. + """ + task_store_key = (request.app_id(), request.queue_name()) + if task_store_key not in admin_console_dummy_tasks: + for _ in request.task_name_list(): + response.add_result( + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE) + return + + store = admin_console_dummy_tasks[task_store_key] + for taskname in request.task_name_list(): + if random.random() <= 0.05: + response.add_result( + taskqueue_service_pb.TaskQueueServiceError.TRANSIENT_ERROR) + else: + response.add_result(store.Delete(taskname)) + + def _Dynamic_DeleteQueue(self, request, response): + """Local delete implementation of TaskQueueService.DeleteQueue. + + Args: + request: A taskqueue_service_pb.TaskQueueDeleteQueueRequest. + response: A taskqueue_service_pb.TaskQueueDeleteQueueResponse. + """ + if not request.queue_name(): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_NAME) + + queues = self._app_queues.get(request.app_id(), {}) + if request.queue_name() not in queues: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE) + elif queues[request.queue_name()] is None: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_QUEUE) + + queues[request.queue_name()] = None + + def _Dynamic_PauseQueue(self, request, response): + """Local pause implementation of TaskQueueService.PauseQueue. + + Args: + request: A taskqueue_service_pb.TaskQueuePauseQueueRequest. + response: A taskqueue_service_pb.TaskQueuePauseQueueResponse. + """ + if not request.queue_name(): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_NAME) + + queues = self._app_queues.get(request.app_id(), {}) + if request.queue_name() != DEFAULT_QUEUE_NAME: + if request.queue_name() not in queues: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE) + elif queues[request.queue_name()] is None: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_QUEUE) + + queues[request.queue_name()].paused = request.pause() + + def _Dynamic_PurgeQueue(self, request, response): + """Local purge implementation of TaskQueueService.PurgeQueue. + + Args: + request: A taskqueue_service_pb.TaskQueuePurgeQueueRequest. + response: A taskqueue_service_pb.TaskQueuePurgeQueueResponse. + """ + if not request.queue_name(): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.INVALID_QUEUE_NAME) + + queues = self._app_queues.get(request.app_id(), {}) + if request.queue_name() != DEFAULT_QUEUE_NAME: + if request.queue_name() not in queues: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.UNKNOWN_QUEUE) + elif queues[request.queue_name()] is None: + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.TOMBSTONED_QUEUE) + + store = self.GetDummyTaskStore(request.app_id(), request.queue_name()) + for task in store.Lookup(store.Count()): + store.Delete(task.task_name()) + + self.FlushQueue(request.queue_name()) + + def _Dynamic_UpdateStorageLimit(self, request, response): + """Local implementation of TaskQueueService.UpdateStorageLimit. + Args: + request: A taskqueue_service_pb.TaskQueueUpdateStorageLimitRequest. + response: A taskqueue_service_pb.TaskQueueUpdateStorageLimitResponse. + """ + if request.limit() < 0 or request.limit() > 1000 * (1024 ** 4): + raise apiproxy_errors.ApplicationError( + taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST) + + response.set_new_limit(request.limit()) diff --git a/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.pyc b/google_appengine/google/appengine/api/labs/taskqueue/taskqueue_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9e7894b88476dd727c4cb619d8d41a59fc5b92a GIT binary patch literal 37855 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%0D}fMV zED%JQ5W>>7W$CYgO5Av&*mrP_gT}Q+!a*~`*c!COUv9yX9``9L_3Ve{^WvH~HAQnt zn1+gro^Pl0vBt7Ta6tUeIU>O7xSXQLC$xf&c)XWvHUmzl<%Pztke=I*Q~nUb#-|>q zhxLcaas54#HajVz#C%gKEwKX%ON*zCOS~GUr>g=t`u3T{SZy3Pc z9q46h@+hE`cfJl|b9ag0^ge6U);nK|D#xOKPId2my{$6F+@p3!r^M`(+w|&N*^W8c z`mHz3S@4iHAG~XU+m?lWcMkwm=dVneb3T}Hi>APA^i7pJe&X?ai1yn!=ga9AzH*F& zw~_bqL`Od21;)kMO$>J^S>@BOe70($qpEr0opmpky=O69-VdjGIrlNL_sFDP-u6AS zyz2wTsYS8MraVXB7O&y$=cv4lk?=|KIQLe*Tfi?Fyn%`$p?4Kv8Q+{C!Seh|L3=I0 zsXjaHLSo$bP)vGq(4nF6*{sc_h*LgF^6gPlq#s~w-*(v+d$Cf%HpQKcyxUCPXO*>a zJVT-RD~F7rYY2KckeYC1*N`m~ykBiB=L z%EeiYH=-vqW7wM_lQ!#a$Ej`jG@t+Ul`$|2!t5kzRtRKI%|3S0sjYj>njHEqCo+V5 zFbbbk_C9b%dhnj{#T4&SY4vAOe&JIR*qj-co;DVs6bHIL)~ApCNR3YJKTw)uF#asN zbBjx77Z#W0&;S8MXl<;+`5$z8t5o;M? zn>cpmF|Ks&#ryktT;X$|n8hRoee7Vjfwrjk)Cr__nSYhm7lB&6)ueXbQ>k5;v*+!? z`TRqZ()?$YbuTzB z>zc2B<(g|ZGKPy}vRT)r!Jq2pEQ@sF*Z-ZMn!tLO&kvAY4Z zI}f1k0pH)*4#TX2@~nvQ?oR?pcL^)m_}>6TAMAnX45lZuBQjDw^oaGR6s8qh;Y?T3 z@?tmKILQ{j?$|`5QS|W_ule9Vd0}1ueRT;H?#Nz#9i$xC7oCwI)=GKZY+b z{@~+uDbVa^IegP{b$|KhXY+8494G4cUEz?!JRk(aV(Ix7Mu$k zp$3`v1b*8qd(U9FQ&rmyroUf+<$-25#0)RiG1vUBPs}qMX2ykg5>Y_!jfmndkdU&p zoS935n^i8^a}pVY`?cxj%Fxy?$#^ulA!aD{S6M4tL zkeLl`qi5#xL`2-&e0bx};_~-!f!&8sN#m22++V{5XB6-@CbtNjMF14XzA$dPhj4iW zm#0!f@JZaA!DRuL7jXGBF6X*fvdaCT`UyzHw1^Lrus_#rGusM z(&ME)rHRu0rODC{m7Xd+R~jh|mJXB-m5xyAe#&ux{vMK5;JL>>HFp$);_n$FUXu5% zx*c5D9(V~CY*>}`fCwrhsH7vHyO7V_b1huJXXN{22Y1Tc?(iql1?+9KZQ+8TF4jWb ze~UYO`df%J+b91gF04;7xV(W2$NF)YAH@B+3uiC1qm9ianlPTN-CiO&8Am>1_(_CX m#?R8F*gtIb{Mf_xxTSvzw2<;+78Cb{iC?5e+fyo*%Kr!N^|M_7 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/lib_config.py b/google_appengine/google/appengine/api/lib_config.py new file mode 100755 index 0000000..89bcd23 --- /dev/null +++ b/google_appengine/google/appengine/api/lib_config.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A mechanism for library configuration. + +Whenever App Engine library code has the need for a user-configurable +value, it should use the following protocol: + +1. Pick a prefix unique to the library module, e.g. 'mylib'. + +2. Call lib_config.register(prefix, mapping) with that prefix as + the first argument and a dict mapping suffixes to default functions + as the second. + +3. The register() function returns a config handle unique to this + prefix. The config handle object has attributes corresponding to + each of the suffixes given in the mapping. Call these functions + (they're not really methods even though they look like methods) to + access the user's configuration value. If the user didn't + configure a function, the default function from the mapping is + called instead. + +4. Document the function name and its signature and semantics. + +Users wanting to provide configuration values should create a module +named appengine_config.py in the top-level directory of their +application, and define functions as documented by various App Engine +library components in that module. To change the configuration, edit +the file and re-deploy the application. (When using the SDK, no +redeployment is required: the development server will pick up the +changes the next time it handles a request.) + +Third party libraries can also use this mechanism. For casual use, +just calling the register() method with a unique prefix is okay. For +carefull libraries, however, it is recommended to instantiate a new +LibConfigRegistry instance using a different module name. + +Example appengine_config.py file: + + from somewhere import MyMiddleWareClass + + def mylib_add_middleware(app): + app = MyMiddleWareClass(app) + return app + +Example library use: + + from google.appengine.api import lib_config + + config_handle = lib_config.register( + 'mylib', + {'add_middleware': lambda app: app}) + + def add_middleware(app): + return config_handle.add_middleware(app) +""" + + +__all__ = ['DEFAULT_MODNAME', + 'LibConfigRegistry', + 'ConfigHandle', + 'register', + 'main', + ] + + +import logging +import os +import sys + + +DEFAULT_MODNAME = 'appengine_config' + + +class LibConfigRegistry(object): + """A registry for library configuration values.""" + + def __init__(self, modname): + """Constructor. + + Args: + modname: The module name to be imported. + + Note: the actual import of this module is deferred until the first + time a configuration value is requested through attribute access + on a ConfigHandle instance. + """ + self._modname = modname + self._registrations = {} + self._module = None + + def register(self, prefix, mapping): + """Register a set of configuration names. + + Args: + prefix: A shared prefix for the configuration names being registered. + If the prefix doesn't end in '_', that character is appended. + mapping: A dict mapping suffix strings to default values. + + Returns: + A ConfigHandle instance. + + It's okay to re-register the same prefix: the mappings are merged, + and for duplicate suffixes the most recent registration wins. + """ + if not prefix.endswith('_'): + prefix += '_' + handle = self._registrations.get(prefix) + if handle is None: + handle = ConfigHandle(prefix, self) + self._registrations[prefix] = handle + handle._update_defaults(mapping) + return handle + + def initialize(self, import_func=__import__): + """Attempt to import the config module, if not already imported. + + This function always sets self._module to a value unequal + to None: either the imported module (if imported successfully), or + a dummy object() instance (if an ImportError was raised). Other + exceptions are *not* caught. + + When a dummy instance is used, it is also put in sys.modules. + This allows us to detect when sys.modules was changed (as + dev_appserver.py does when it notices source code changes) and + re-try the __import__ in that case, while skipping it (for speed) + if nothing has changed. + + Args: + import_func: Used for dependency injection. + """ + if (self._module is not None and + self._module is sys.modules.get(self._modname)): + return + try: + import_func(self._modname) + except ImportError, err: + if str(err) != 'No module named %s' % self._modname: + raise + self._module = object() + sys.modules[self._modname] = self._module + else: + self._module = sys.modules[self._modname] + + def _pairs(self, prefix): + """Generate (key, value) pairs from the config module matching prefix. + + Args: + prefix: A prefix string ending in '_', e.g. 'mylib_'. + + Yields: + (key, value) pairs where key is the configuration name with + prefix removed, and value is the corresponding value. + """ + mapping = getattr(self._module, '__dict__', None) + if not mapping: + return + nskip = len(prefix) + for key, value in mapping.iteritems(): + if key.startswith(prefix): + yield key[nskip:], value + + def _dump(self): + """Print info about all registrations to stdout.""" + self.initialize() + if not hasattr(self._module, '__dict__'): + print 'Module %s.py does not exist.' % self._modname + elif not self._registrations: + print 'No registrations for %s.py.' % self._modname + else: + print 'Registrations in %s.py:' % self._modname + print '-'*40 + for prefix in sorted(self._registrations): + self._registrations[prefix]._dump() + + +class ConfigHandle(object): + """A set of configuration for a single library module or package. + + Public attributes of instances of this class are configuration + values. Attributes are dynamically computed (in __getattr__()) and + cached as regular instance attributes. + """ + + _initialized = False + + def __init__(self, prefix, registry): + """Constructor. + + Args: + prefix: A shared prefix for the configuration names being registered. + It *must* end in '_'. (This is enforced by LibConfigRegistry.) + registry: A LibConfigRegistry instance. + """ + assert prefix.endswith('_') + self._prefix = prefix + self._defaults = {} + self._overrides = {} + self._registry = registry + + def _update_defaults(self, mapping): + """Update the default mappings. + + Args: + mapping: A dict mapping suffix strings to default values. + """ + for key, value in mapping.iteritems(): + if key.startswith('__') and key.endswith('__'): + continue + self._defaults[key] = value + if self._initialized: + self._update_configs() + + def _update_configs(self): + """Update the configuration values. + + This clears the cached values, initializes the registry, and loads + the configuration values from the config module. + """ + if self._initialized: + self._clear_cache() + self._registry.initialize() + for key, value in self._registry._pairs(self._prefix): + if key not in self._defaults: + logging.warn('Configuration "%s" not recognized', self._prefix + key) + else: + self._overrides[key] = value + self._initialized = True + + def _clear_cache(self): + """Clear the cached values.""" + for key in self._defaults: + try: + delattr(self, key) + except AttributeError: + pass + + def _dump(self): + """Print info about this set of registrations to stdout.""" + print 'Prefix %s:' % self._prefix + if self._overrides: + print ' Overrides:' + for key in sorted(self._overrides): + print ' %s = %r' % (key, self._overrides[key]) + else: + print ' No overrides' + if self._defaults: + print ' Defaults:' + for key in sorted(self._defaults): + print ' %s = %r' % (key, self._defaults[key]) + else: + print ' No defaults' + print '-'*40 + + def __getattr__(self, suffix): + """Dynamic attribute access. + + Args: + suffix: The attribute name. + + Returns: + A configuration values. + + Raises: + AttributeError if the suffix is not a registered suffix. + + The first time an attribute is referenced, this method is invoked. + The value returned taken either from the config module or from the + registered default. + """ + if not self._initialized: + self._update_configs() + if suffix in self._overrides: + value = self._overrides[suffix] + elif suffix in self._defaults: + value = self._defaults[suffix] + else: + raise AttributeError(suffix) + setattr(self, suffix, value) + return value + + +_default_registry = LibConfigRegistry(DEFAULT_MODNAME) + + +def register(prefix, mapping): + """Register a set of configurations with the default config module. + + Args: + prefix: A shared prefix for the configuration names being registered. + If the prefix doesn't end in '_', that character is appended. + mapping: A dict mapping suffix strings to default values. + + Returns: + A ConfigHandle instance. + """ + return _default_registry.register(prefix, mapping) + + +def main(): + """CGI-style request handler to dump the configuration. + + Put this in your app.yaml to enable (you can pick any URL): + + - url: /lib_config + script: $PYTHON_LIB/google/appengine/api/lib_config.py + + Note: unless you are using the SDK, you must be admin. + """ + if not os.getenv('SERVER_SOFTWARE', '').startswith('Dev'): + from google.appengine.api import users + if not users.is_current_user_admin(): + if users.get_current_user() is None: + print 'Status: 302' + print 'Location:', users.create_login_url(os.getenv('PATH_INFO', '')) + else: + print 'Status: 403' + print + print 'Forbidden' + return + + print 'Content-type: text/plain' + print + _default_registry._dump() + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/api/lib_config.pyc b/google_appengine/google/appengine/api/lib_config.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f782330fffdadbaea725afc29b4a7c0826494287 GIT binary patch literal 11633 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/mail.py b/google_appengine/google/appengine/api/mail.py new file mode 100755 index 0000000..10ee784 --- /dev/null +++ b/google_appengine/google/appengine/api/mail.py @@ -0,0 +1,1180 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Sends email on behalf of application. + +Provides functions for application developers to provide email services +for their applications. Also provides a few utility methods. +""" + + + + + + +import email +from email import MIMEBase +from email import MIMEMultipart +from email import MIMEText +from email import Parser +import logging + +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import mail_service_pb +from google.appengine.api import users +from google.appengine.api.mail_errors import * +from google.appengine.runtime import apiproxy_errors + + + +ERROR_MAP = { + mail_service_pb.MailServiceError.BAD_REQUEST: + BadRequestError, + + mail_service_pb.MailServiceError.UNAUTHORIZED_SENDER: + InvalidSenderError, + + mail_service_pb.MailServiceError.INVALID_ATTACHMENT_TYPE: + InvalidAttachmentTypeError, +} + + +EXTENSION_MIME_MAP = { + 'aif': 'audio/x-aiff', + 'aifc': 'audio/x-aiff', + 'aiff': 'audio/x-aiff', + 'asc': 'text/plain', + 'au': 'audio/basic', + 'avi': 'video/x-msvideo', + 'bmp': 'image/x-ms-bmp', + 'css': 'text/css', + 'csv': 'text/csv', + 'doc': 'application/msword', + 'diff': 'text/plain', + 'flac': 'audio/flac', + 'gif': 'image/gif', + 'htm': 'text/html', + 'html': 'text/html', + 'ics': 'text/calendar', + 'jpe': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'jpg': 'image/jpeg', + 'm4a': 'audio/mp4', + 'mid': 'audio/mid', + 'mov': 'video/quicktime', + 'mp3': 'audio/mpeg', + 'mp4': 'video/mp4', + 'mpe': 'video/mpeg', + 'mpeg': 'video/mpeg', + 'mpg': 'video/mpeg', + 'odp': 'application/vnd.oasis.opendocument.presentation', + 'ods': 'application/vnd.oasis.opendocument.spreadsheet', + 'odt': 'application/vnd.oasis.opendocument.text', + 'oga': 'audio/ogg', + 'ogg': 'audio/ogg', + 'ogv': 'video/ogg', + 'pdf': 'application/pdf', + 'png': 'image/png', + 'pot': 'text/plain', + 'pps': 'application/vnd.ms-powerpoint', + 'ppt': 'application/vnd.ms-powerpoint', + 'qt': 'video/quicktime', + 'rmi': 'audio/mid', + 'rss': 'text/rss+xml', + 'snd': 'audio/basic', + 'sxc': 'application/vnd.sun.xml.calc', + 'sxw': 'application/vnd.sun.xml.writer', + 'text': 'text/plain', + 'tif': 'image/tiff', + 'tiff': 'image/tiff', + 'txt': 'text/plain', + 'vcf': 'text/directory', + 'wav': 'audio/x-wav', + 'wbmp': 'image/vnd.wap.wbmp', + 'xls': 'application/vnd.ms-excel', + } + +EXTENSION_WHITELIST = frozenset(EXTENSION_MIME_MAP.iterkeys()) + + +def invalid_email_reason(email_address, field): + """Determine reason why email is invalid. + + Args: + email_address: Email to check. + field: Field that is invalid. + + Returns: + String indicating invalid email reason if there is one, + else None. + """ + if email_address is None: + return 'None email address for %s.' % field + + if isinstance(email_address, users.User): + email_address = email_address.email() + if not isinstance(email_address, basestring): + return 'Invalid email address type for %s.' % field + stripped_address = email_address.strip() + if not stripped_address: + return 'Empty email address for %s.' % field + return None + +InvalidEmailReason = invalid_email_reason + + +def is_email_valid(email_address): + """Determine if email is invalid. + + Args: + email_address: Email to check. + + Returns: + True if email is valid, else False. + """ + return invalid_email_reason(email_address, '') is None + +IsEmailValid = is_email_valid + + +def check_email_valid(email_address, field): + """Check that email is valid. + + Args: + email_address: Email to check. + field: Field to check. + + Raises: + InvalidEmailError if email_address is invalid. + """ + reason = invalid_email_reason(email_address, field) + if reason is not None: + raise InvalidEmailError(reason) + +CheckEmailValid = check_email_valid + + +def _email_check_and_list(emails, field): + """Generate a list of emails. + + Args: + emails: Single email or list of emails. + + Returns: + Sequence of email addresses. + + Raises: + InvalidEmailError if any email addresses are invalid. + """ + if isinstance(emails, types.StringTypes): + check_email_valid(value) + else: + for address in iter(emails): + check_email_valid(address, field) + + +def _email_sequence(emails): + """Forces email to be sequenceable type. + + Iterable values are returned as is. This function really just wraps the case + where there is a single email string. + + Args: + emails: Emails (or email) to coerce to sequence. + + Returns: + Single tuple with email in it if only one email string provided, + else returns emails as is. + """ + if isinstance(emails, basestring): + return emails, + return emails + + +def _attachment_sequence(attachments): + """Forces attachments to be sequenceable type. + + Iterable values are returned as is. This function really just wraps the case + where there is a single attachment. + + Args: + attachments: Attachments (or attachment) to coerce to sequence. + + Returns: + Single tuple with attachment tuple in it if only one attachment provided, + else returns attachments as is. + """ + if len(attachments) == 2 and isinstance(attachments[0], basestring): + return attachments, + return attachments + + +def _parse_mime_message(mime_message): + """Helper function converts a mime_message in to email.Message.Message. + + Args: + mime_message: MIME Message, string or file containing mime message. + + Returns: + Instance of email.Message.Message. Will return mime_message if already + an instance. + """ + if isinstance(mime_message, email.Message.Message): + return mime_message + elif isinstance(mime_message, basestring): + return email.message_from_string(mime_message) + else: + return email.message_from_file(mime_message) + + +def send_mail(sender, + to, + subject, + body, + make_sync_call=apiproxy_stub_map.MakeSyncCall, + **kw): + """Sends mail on behalf of application. + + Args: + sender: Sender email address as appears in the 'from' email line. + to: List of 'to' addresses or a single address. + subject: Message subject string. + body: Body of type text/plain. + make_sync_call: Function used to make sync call to API proxy. + kw: Keyword arguments compatible with EmailMessage keyword based + constructor. + + Raises: + InvalidEmailError when invalid email address provided. + """ + kw['sender'] = sender + kw['to'] = to + kw['subject'] = subject + kw['body'] = body + message = EmailMessage(**kw) + message.send(make_sync_call) + +SendMail = send_mail + + +def send_mail_to_admins(sender, + subject, + body, + make_sync_call=apiproxy_stub_map.MakeSyncCall, + **kw): + """Sends mail to admins on behalf of application. + + Args: + sender: Sender email address as appears in the 'from' email line. + subject: Message subject string. + body: Body of type text/plain. + make_sync_call: Function used to make sync call to API proxy. + kw: Keyword arguments compatible with EmailMessage keyword based + constructor. + + Raises: + InvalidEmailError when invalid email address provided. + """ + kw['sender'] = sender + kw['subject'] = subject + kw['body'] = body + message = AdminEmailMessage(**kw) + message.send(make_sync_call) + +SendMailToAdmins = send_mail_to_admins + + +def _GetMimeType(file_name): + """Determine mime-type from file name. + + Parses file name and determines mime-type based on extension map. + + This method is not part of the public API and should not be used by + applications. + + Args: + file_name: File to determine extension for. + + Returns: + Mime-type associated with file extension. + + Raises: + InvalidAttachmentTypeError when the file name of an attachment. + """ + extension_index = file_name.rfind('.') + if extension_index == -1: + raise InvalidAttachmentTypeError( + "File '%s' does not have an extension" % file_name) + extension = file_name[extension_index + 1:].lower() + mime_type = EXTENSION_MIME_MAP.get(extension, None) + if mime_type is None: + raise InvalidAttachmentTypeError( + "Extension '%s' is not supported." % extension) + return mime_type + + +def mail_message_to_mime_message(protocol_message): + """Generate a MIMEMultitype message from protocol buffer. + + Generates a complete MIME multi-part email object from a MailMessage + protocol buffer. The body fields are sent as individual alternatives + if they are both present, otherwise, only one body part is sent. + + Multiple entry email fields such as 'To', 'Cc' and 'Bcc' are converted + to a list of comma separated email addresses. + + Args: + protocol_message: Message PB to convert to MIMEMultitype. + + Returns: + MIMEMultitype representing the provided MailMessage. + + Raises: + InvalidAttachmentTypeError when the file name of an attachment + """ + parts = [] + if protocol_message.has_textbody(): + parts.append(MIMEText.MIMEText(protocol_message.textbody())) + if protocol_message.has_htmlbody(): + parts.append(MIMEText.MIMEText(protocol_message.htmlbody(), + _subtype='html')) + + if len(parts) == 1: + payload = parts + else: + payload = [MIMEMultipart.MIMEMultipart('alternative', _subparts=parts)] + + result = MIMEMultipart.MIMEMultipart(_subparts=payload) + for attachment in protocol_message.attachment_list(): + file_name = attachment.filename() + mime_type = _GetMimeType(file_name) + maintype, subtype = mime_type.split('/') + mime_attachment = MIMEBase.MIMEBase(maintype, subtype) + mime_attachment.add_header('Content-Disposition', + 'attachment', + filename=attachment.filename()) + mime_attachment.set_payload(attachment.data()) + result.attach(mime_attachment) + + if protocol_message.to_size(): + result['To'] = ', '.join(protocol_message.to_list()) + if protocol_message.cc_size(): + result['Cc'] = ', '.join(protocol_message.cc_list()) + if protocol_message.bcc_size(): + result['Bcc'] = ', '.join(protocol_message.bcc_list()) + + result['From'] = protocol_message.sender() + result['Reply-To'] = protocol_message.replyto() + result['Subject'] = protocol_message.subject() + + return result + +MailMessageToMIMEMessage = mail_message_to_mime_message + + +def _to_str(value): + """Helper function to make sure unicode values converted to utf-8. + + Args: + value: str or unicode to convert to utf-8. + + Returns: + UTF-8 encoded str of value, otherwise value unchanged. + """ + if isinstance(value, unicode): + return value.encode('utf-8') + return value + + +class EncodedPayload(object): + """Wrapper for a payload that contains encoding information. + + When an email is recieved, it is usually encoded using a certain + character set, and then possibly further encoded using a transfer + encoding in that character set. Most of the times, it is possible + to decode the encoded payload as is, however, in the case where it + is not, the encoded payload and the original encoding information + must be preserved. + + Attributes: + payload: The original encoded payload. + charset: The character set of the encoded payload. None means use + default character set. + encoding: The transfer encoding of the encoded payload. None means + content not encoded. + """ + + def __init__(self, payload, charset=None, encoding=None): + """Constructor. + + Args: + payload: Maps to attribute of the same name. + charset: Maps to attribute of the same name. + encoding: Maps to attribute of the same name. + """ + self.payload = payload + self.charset = charset + self.encoding = encoding + + def decode(self): + """Attempt to decode the encoded data. + + Attempt to use pythons codec library to decode the payload. All + exceptions are passed back to the caller. + + Returns: + Binary or unicode version of payload content. + """ + payload = self.payload + + if self.encoding and self.encoding.lower() != '7bit': + try: + payload = payload.decode(self.encoding) + except LookupError: + raise UnknownEncodingError('Unknown decoding %s.' % self.encoding) + except (Exception, Error), e: + raise PayloadEncodingError('Could not decode payload: %s' % e) + + if self.charset and str(self.charset).lower() != '7bit': + try: + payload = payload.decode(str(self.charset)) + except LookupError: + raise UnknownCharsetError('Unknown charset %s.' % self.charset) + except (Exception, Error), e: + raise PayloadEncodingError('Could read characters: %s' % e) + + return payload + + def __eq__(self, other): + """Equality operator. + + Args: + other: The other EncodedPayload object to compare with. Comparison + with other object types are not implemented. + + Returns: + True of payload and encodings are equal, else false. + """ + if isinstance(other, EncodedPayload): + return (self.payload == other.payload and + self.charset == other.charset and + self.encoding == other.encoding) + else: + return NotImplemented + + def copy_to(self, mime_message): + """Copy contents to MIME message payload. + + If no content transfer encoding is specified, and the character set does + not equal the over-all message encoding, the payload will be base64 + encoded. + + Args: + mime_message: Message instance to receive new payload. + """ + if self.encoding: + mime_message['content-transfer-encoding'] = self.encoding + mime_message.set_payload(self.payload, self.charset) + + def to_mime_message(self): + """Convert to MIME message. + + Returns: + MIME message instance of payload. + """ + mime_message = email.Message.Message() + self.copy_to(mime_message) + return mime_message + + def __str__(self): + """String representation of encoded message. + + Returns: + MIME encoded representation of encoded payload as an independent message. + """ + return str(self.to_mime_message()) + + def __repr__(self): + """Basic representation of encoded payload. + + Returns: + Payload itself is represented by its hash value. + """ + result = '' + + +class _EmailMessageBase(object): + """Base class for email API service objects. + + Subclasses must define a class variable called _API_CALL with the name + of its underlying mail sending API call. + """ + + PROPERTIES = set([ + 'sender', + 'reply_to', + 'subject', + 'body', + 'html', + 'attachments', + ]) + + PROPERTIES.update(('to', 'cc', 'bcc')) + + def __init__(self, mime_message=None, **kw): + """Initialize Email message. + + Creates new MailMessage protocol buffer and initializes it with any + keyword arguments. + + Args: + mime_message: MIME message to initialize from. If instance of + email.Message.Message will take ownership as original message. + kw: List of keyword properties as defined by PROPERTIES. + """ + if mime_message: + mime_message = _parse_mime_message(mime_message) + self.update_from_mime_message(mime_message) + self.__original = mime_message + + self.initialize(**kw) + + @property + def original(self): + """Get original MIME message from which values were set.""" + return self.__original + + def initialize(self, **kw): + """Keyword initialization. + + Used to set all fields of the email message using keyword arguments. + + Args: + kw: List of keyword properties as defined by PROPERTIES. + """ + for name, value in kw.iteritems(): + setattr(self, name, value) + + def Initialize(self, **kw): + self.initialize(**kw) + + def check_initialized(self): + """Check if EmailMessage is properly initialized. + + Test used to determine if EmailMessage meets basic requirements + for being used with the mail API. This means that the following + fields must be set or have at least one value in the case of + multi value fields: + + - Subject must be set. + - A recipient must be specified. + - Must contain a body. + - All bodies and attachments must decode properly. + + This check does not include determining if the sender is actually + authorized to send email for the application. + + Raises: + Appropriate exception for initialization failure. + + InvalidAttachmentTypeError: Use of incorrect attachment type. + MissingRecipientsError: No recipients specified in to, cc or bcc. + MissingSenderError: No sender specified. + MissingSubjectError: Subject is not specified. + MissingBodyError: No body specified. + PayloadEncodingError: Payload is not properly encoded. + UnknownEncodingError: Payload has unknown encoding. + UnknownCharsetError: Payload has unknown character set. + """ + if not hasattr(self, 'sender'): + raise MissingSenderError() + if not hasattr(self, 'subject'): + raise MissingSubjectError() + + found_body = False + + try: + body = self.body + except AttributeError: + pass + else: + if isinstance(body, EncodedPayload): + body.decode() + found_body = True + + try: + html = self.html + except AttributeError: + pass + else: + if isinstance(html, EncodedPayload): + html.decode() + found_body = True + + if not found_body: + raise MissingBodyError() + + if hasattr(self, 'attachments'): + for file_name, data in _attachment_sequence(self.attachments): + _GetMimeType(file_name) + + if isinstance(data, EncodedPayload): + data.decode() + + def CheckInitialized(self): + self.check_initialized() + + def is_initialized(self): + """Determine if EmailMessage is properly initialized. + + Returns: + True if message is properly initializes, otherwise False. + """ + try: + self.check_initialized() + return True + except Error: + return False + + def IsInitialized(self): + return self.is_initialized() + + def ToProto(self): + """Convert mail message to protocol message. + + Unicode strings are converted to UTF-8 for all fields. + + This method is overriden by EmailMessage to support the sender fields. + + Returns: + MailMessage protocol version of mail message. + + Raises: + Passes through decoding errors that occur when using when decoding + EncodedPayload objects. + """ + self.check_initialized() + message = mail_service_pb.MailMessage() + message.set_sender(_to_str(self.sender)) + + if hasattr(self, 'reply_to'): + message.set_replyto(_to_str(self.reply_to)) + message.set_subject(_to_str(self.subject)) + + if hasattr(self, 'body'): + body = self.body + if isinstance(body, EncodedPayload): + body = body.decode() + message.set_textbody(_to_str(body)) + if hasattr(self, 'html'): + html = self.html + if isinstance(html, EncodedPayload): + html = html.decode() + message.set_htmlbody(_to_str(html)) + + if hasattr(self, 'attachments'): + for file_name, data in _attachment_sequence(self.attachments): + if isinstance(data, EncodedPayload): + data = data.decode() + attachment = message.add_attachment() + attachment.set_filename(_to_str(file_name)) + attachment.set_data(_to_str(data)) + return message + + def to_mime_message(self): + """Generate a MIMEMultitype message from EmailMessage. + + Calls MailMessageToMessage after converting self to protocol + buffer. Protocol buffer is better at handing corner cases + than EmailMessage class. + + Returns: + MIMEMultitype representing the provided MailMessage. + + Raises: + Appropriate exception for initialization failure. + + InvalidAttachmentTypeError: Use of incorrect attachment type. + MissingSenderError: No sender specified. + MissingSubjectError: Subject is not specified. + MissingBodyError: No body specified. + """ + return mail_message_to_mime_message(self.ToProto()) + + def ToMIMEMessage(self): + return self.to_mime_message() + + def send(self, make_sync_call=apiproxy_stub_map.MakeSyncCall): + """Send email message. + + Send properly initialized email message via email API. + + Args: + make_sync_call: Method which will make synchronous call to api proxy. + + Raises: + Errors defined in this file above. + """ + message = self.ToProto() + response = api_base_pb.VoidProto() + + try: + make_sync_call('mail', self._API_CALL, message, response) + except apiproxy_errors.ApplicationError, e: + if e.application_error in ERROR_MAP: + raise ERROR_MAP[e.application_error](e.error_detail) + raise e + + def Send(self, *args, **kwds): + self.send(*args, **kwds) + + def _check_attachment(self, attachment): + file_name, data = attachment + if not (isinstance(file_name, basestring) or + isinstance(data, basestring)): + raise TypeError() + + def _check_attachments(self, attachments): + """Checks values going to attachment field. + + Mainly used to check type safety of the values. Each value of the list + must be a pair of the form (file_name, data), and both values a string + type. + + Args: + attachments: Collection of attachment tuples. + + Raises: + TypeError if values are not string type. + """ + if len(attachments) == 2 and isinstance(attachments[0], basestring): + self._check_attachment(attachments) + else: + for attachment in attachments: + self._check_attachment(attachment) + + def __setattr__(self, attr, value): + """Property setting access control. + + Controls write access to email fields. + + Args: + attr: Attribute to access. + value: New value for field. + + Raises: + ValueError: If provided with an empty field. + AttributeError: If not an allowed assignment field. + """ + if not attr.startswith('_EmailMessageBase'): + if attr in ['sender', 'reply_to']: + check_email_valid(value, attr) + + if not value: + raise ValueError('May not set empty value for \'%s\'' % attr) + + if attr not in self.PROPERTIES: + raise AttributeError('\'EmailMessage\' has no attribute \'%s\'' % attr) + + if attr == 'attachments': + self._check_attachments(value) + + super(_EmailMessageBase, self).__setattr__(attr, value) + + def _add_body(self, content_type, payload): + """Add body to email from payload. + + Will overwrite any existing default plain or html body. + + Args: + content_type: Content-type of body. + payload: Payload to store body as. + """ + if content_type == 'text/plain': + self.body = payload + elif content_type == 'text/html': + self.html = payload + + def _update_payload(self, mime_message): + """Update payload of mail message from mime_message. + + This function works recusively when it receives a multipart body. + If it receives a non-multi mime object, it will determine whether or + not it is an attachment by whether it has a filename or not. Attachments + and bodies are then wrapped in EncodedPayload with the correct charsets and + encodings. + + Args: + mime_message: A Message MIME email object. + """ + payload = mime_message.get_payload() + + if payload: + if mime_message.get_content_maintype() == 'multipart': + for alternative in payload: + self._update_payload(alternative) + else: + filename = mime_message.get_param('filename', + header='content-disposition') + if not filename: + filename = mime_message.get_param('name') + + payload = EncodedPayload(payload, + (mime_message.get_content_charset() or + mime_message.get_charset()), + mime_message['content-transfer-encoding']) + + if filename: + try: + attachments = self.attachments + except AttributeError: + self.attachments = [(filename, payload)] + else: + if isinstance(attachments[0], basestring): + self.attachments = [attachments] + attachments = self.attachments + attachments.append((filename, payload)) + else: + self._add_body(mime_message.get_content_type(), payload) + + def update_from_mime_message(self, mime_message): + """Copy information from a mime message. + + Set information of instance to values of mime message. This method + will only copy values that it finds. Any missing values will not + be copied, nor will they overwrite old values with blank values. + + This object is not guaranteed to be initialized after this call. + + Args: + mime_message: email.Message instance to copy information from. + + Returns: + MIME Message instance of mime_message argument. + """ + mime_message = _parse_mime_message(mime_message) + + sender = mime_message['from'] + if sender: + self.sender = sender + + reply_to = mime_message['reply-to'] + if reply_to: + self.reply_to = reply_to + + subject = mime_message['subject'] + if subject: + self.subject = subject + + self._update_payload(mime_message) + + def bodies(self, content_type=None): + """Iterate over all bodies. + + Yields: + Tuple (content_type, payload) for html and body in that order. + """ + if (not content_type or + content_type == 'text' or + content_type == 'text/html'): + try: + yield 'text/html', self.html + except AttributeError: + pass + + if (not content_type or + content_type == 'text' or + content_type == 'text/plain'): + try: + yield 'text/plain', self.body + except AttributeError: + pass + + +class EmailMessage(_EmailMessageBase): + """Main interface to email API service. + + This class is used to programmatically build an email message to send via + the Mail API. The usage is to construct an instance, populate its fields + and call Send(). + + Example Usage: + An EmailMessage can be built completely by the constructor. + + EmailMessage(sender='sender@nowhere.com', + to='recipient@nowhere.com', + subject='a subject', + body='This is an email to you').Send() + + It might be desirable for an application to build an email in different + places throughout the code. For this, EmailMessage is mutable. + + message = EmailMessage() + message.sender = 'sender@nowhere.com' + message.to = ['recipient1@nowhere.com', 'recipient2@nowhere.com'] + message.subject = 'a subject' + message.body = 'This is an email to you') + message.check_initialized() + message.send() + """ + + _API_CALL = 'Send' + PROPERTIES = set(_EmailMessageBase.PROPERTIES) + + def check_initialized(self): + """Provide additional checks to ensure recipients have been specified. + + Raises: + MissingRecipientError when no recipients specified in to, cc or bcc. + """ + if (not hasattr(self, 'to') and + not hasattr(self, 'cc') and + not hasattr(self, 'bcc')): + raise MissingRecipientsError() + super(EmailMessage, self).check_initialized() + + def CheckInitialized(self): + self.check_initialized() + + def ToProto(self): + """Does addition conversion of recipient fields to protocol buffer. + + Returns: + MailMessage protocol version of mail message including sender fields. + """ + message = super(EmailMessage, self).ToProto() + + for attribute, adder in (('to', message.add_to), + ('cc', message.add_cc), + ('bcc', message.add_bcc)): + if hasattr(self, attribute): + for address in _email_sequence(getattr(self, attribute)): + adder(_to_str(address)) + return message + + def __setattr__(self, attr, value): + """Provides additional checks on recipient fields.""" + if attr in ['to', 'cc', 'bcc']: + if isinstance(value, basestring): + check_email_valid(value, attr) + else: + for address in value: + check_email_valid(address, attr) + + super(EmailMessage, self).__setattr__(attr, value) + + def update_from_mime_message(self, mime_message): + """Copy information from a mime message. + + Update fields for recipients. + + Args: + mime_message: email.Message instance to copy information from. + """ + mime_message = _parse_mime_message(mime_message) + super(EmailMessage, self).update_from_mime_message(mime_message) + + to = mime_message.get_all('to') + if to: + if len(to) == 1: + self.to = to[0] + else: + self.to = to + + cc = mime_message.get_all('cc') + if cc: + if len(cc) == 1: + self.cc = cc[0] + else: + self.cc = cc + + bcc = mime_message.get_all('bcc') + if bcc: + if len(bcc) == 1: + self.bcc = bcc[0] + else: + self.bcc = bcc + + +class AdminEmailMessage(_EmailMessageBase): + """Interface to sending email messages to all admins via the amil API. + + This class is used to programmatically build an admin email message to send + via the Mail API. The usage is to construct an instance, populate its fields + and call Send(). + + Unlike the normal email message, addresses in the recipient fields are + ignored and not used for sending. + + Example Usage: + An AdminEmailMessage can be built completely by the constructor. + + AdminEmailMessage(sender='sender@nowhere.com', + subject='a subject', + body='This is an email to you').Send() + + It might be desirable for an application to build an admin email in + different places throughout the code. For this, AdminEmailMessage is + mutable. + + message = AdminEmailMessage() + message.sender = 'sender@nowhere.com' + message.subject = 'a subject' + message.body = 'This is an email to you') + message.check_initialized() + message.send() + """ + + _API_CALL = 'SendToAdmins' + __UNUSED_PROPERTIES = set(('to', 'cc', 'bcc')) + + def __setattr__(self, attr, value): + if attr in self.__UNUSED_PROPERTIES: + logging.warning('\'%s\' is not a valid property to set ' + 'for AdminEmailMessage. It is unused.', attr) + super(AdminEmailMessage, self).__setattr__(attr, value) + + +class InboundEmailMessage(EmailMessage): + """Parsed email object as recevied from external source. + + Has a date field and can store any number of additional bodies. These + additional attributes make the email more flexible as required for + incoming mail, where the developer has less control over the content. + + Example Usage: + + # Read mail message from CGI input. + message = InboundEmailMessage(sys.stdin.read()) + logging.info('Received email message from %s at %s', + message.sender, + message.date) + enriched_body = list(message.bodies('text/enriched'))[0] + ... Do something with body ... + """ + + __HEADER_PROPERTIES = {'date': 'date', + 'message_id': 'message-id', + } + + PROPERTIES = frozenset(_EmailMessageBase.PROPERTIES | + set(('alternate_bodies',)) | + set(__HEADER_PROPERTIES.iterkeys())) + + def update_from_mime_message(self, mime_message): + """Update values from MIME message. + + Copies over date values. + + Args: + mime_message: email.Message instance to copy information from. + """ + mime_message = _parse_mime_message(mime_message) + super(InboundEmailMessage, self).update_from_mime_message(mime_message) + + for property, header in InboundEmailMessage.__HEADER_PROPERTIES.iteritems(): + value = mime_message[header] + if value: + setattr(self, property, value) + + def _add_body(self, content_type, payload): + """Add body to inbound message. + + Method is overidden to handle incoming messages that have more than one + plain or html bodies or has any unidentified bodies. + + This method will not overwrite existing html and body values. This means + that when updating, the text and html bodies that are first in the MIME + document order are assigned to the body and html properties. + + Args: + content_type: Content-type of additional body. + payload: Content of additional body. + """ + if (content_type == 'text/plain' and not hasattr(self, 'body') or + content_type == 'text/html' and not hasattr(self, 'html')): + super(InboundEmailMessage, self)._add_body(content_type, payload) + else: + try: + alternate_bodies = self.alternate_bodies + except AttributeError: + alternate_bodies = self.alternate_bodies = [(content_type, payload)] + else: + alternate_bodies.append((content_type, payload)) + + def bodies(self, content_type=None): + """Iterate over all bodies. + + Args: + content_type: Content type to filter on. Allows selection of only + specific types of content. Can be just the base type of the content + type. For example: + content_type = 'text/html' # Matches only HTML content. + content_type = 'text' # Matches text of any kind. + + Yields: + Tuple (content_type, payload) for all bodies of message, including body, + html and all alternate_bodies in that order. + """ + main_bodies = super(InboundEmailMessage, self).bodies(content_type) + for payload_type, payload in main_bodies: + yield payload_type, payload + + partial_type = bool(content_type and content_type.find('/') < 0) + + try: + for payload_type, payload in self.alternate_bodies: + if content_type: + if partial_type: + match_type = payload_type.split('/')[0] + else: + match_type = payload_type + match = match_type == content_type + else: + match = True + + if match: + yield payload_type, payload + except AttributeError: + pass + + def to_mime_message(self): + """Convert to MIME message. + + Adds additional headers from inbound email. + + Returns: + MIME message instance of payload. + """ + mime_message = super(InboundEmailMessage, self).to_mime_message() + + for property, header in InboundEmailMessage.__HEADER_PROPERTIES.iteritems(): + try: + mime_message[header] = getattr(self, property) + except AttributeError: + pass + + return mime_message + + +Parser.Parser diff --git a/google_appengine/google/appengine/api/mail.pyc b/google_appengine/google/appengine/api/mail.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7bc21601e753de297677956d25e90bc2605046b GIT binary patch literal 38392 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 DciQhU literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/mail_errors.py b/google_appengine/google/appengine/api/mail_errors.py new file mode 100755 index 0000000..6d2b9c3 --- /dev/null +++ b/google_appengine/google/appengine/api/mail_errors.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Exceptions raised my mail API.""" + + +class Error(Exception): + """Base Mail error type.""" + +class BadRequestError(Error): + """Email is not valid.""" + +class InvalidSenderError(Error): + """Sender is not a permitted to send mail for this application.""" + +class InvalidEmailError(Error): + """Bad email set on an email field.""" + +class InvalidAttachmentTypeError(Error): + """Invalid file type for attachments. We don't send viruses!""" + +class MissingRecipientsError(Error): + """No recipients specified in message.""" + +class MissingSenderError(Error): + """No sender specified in message.""" + +class MissingSubjectError(Error): + """Subject not specified in message.""" + +class MissingBodyError(Error): + """No body specified in message.""" + +class PayloadEncodingError(Error): + """Unknown payload encoding.""" + +class UnknownEncodingError(PayloadEncodingError): + """Raised when encoding is not known.""" + +class UnknownCharsetError(PayloadEncodingError): + """Raised when charset is not known.""" diff --git a/google_appengine/google/appengine/api/mail_errors.pyc b/google_appengine/google/appengine/api/mail_errors.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05d49160663002f8732f11aea940c78614ebd2f8 GIT binary patch literal 3391 zcwWtxTTc@~6vt0nDA00k0Ra^UHK8%FH8JtQ5F;oL7$Kx+jJ!0%&d`o_XV#r5*k}EM z#&6;$@XT!YBJpKONNLlv-T%&72_Uq4F<@Y`I-hT06S0=pBdYl%7sY#mM-8#XtaX!gH* zJTO8@O+qR(XT%>8pNc-&Jw9lfN!uh0RhVQJ^9g&l_dFVonJu{d7NLF8u+Q|0Z9p|A zht>6nVU@f92N?d_DiWy4j9a z-l|L4SCr{iSE(-cra{1ES4ch*TB$$-(XvP#7mRKO!?yKl1Jy+|<$_soL9;ghHsIRi zB(9^`cR3T9NM*|QO6jK zl7NT4Fa}48QAFdzXt))*3DmyE4UB3AKoD6|%`+vc7Kgbg@n5+0#BQY1|J6CL1xEH)!T5}#{L zyL=pFw~OcF3~eo?z1IAG5RvL=ir*H>8FaUup{!>p2WMZnmv@1-17y_GEn>F?)NX%I zGN6ko=$>N3oRs&$74r;x-WtLxx_g=-T*k{W9a@k3(o+mcb5gFO`6$0ijEe-3C&EXr z#z8oX?w(yC9L0A{!iP5U=qz^G<1(R(w_xXc9H?{X?s*2ao}#|#(GWMsoTOV#(ex@W7nqS% 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MailAttachment(ProtocolBuffer.ProtocolMessage): + has_filename_ = 0 + filename_ = "" + has_data_ = 0 + data_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def filename(self): return self.filename_ + + def set_filename(self, x): + self.has_filename_ = 1 + self.filename_ = x + + def clear_filename(self): + if self.has_filename_: + self.has_filename_ = 0 + self.filename_ = "" + + def has_filename(self): return self.has_filename_ + + def data(self): return self.data_ + + def set_data(self, x): + self.has_data_ = 1 + self.data_ = x + + def clear_data(self): + if self.has_data_: + self.has_data_ = 0 + self.data_ = "" + + def has_data(self): return self.has_data_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_filename()): self.set_filename(x.filename()) + if (x.has_data()): self.set_data(x.data()) + + def Equals(self, x): + if x is self: return 1 + if self.has_filename_ != x.has_filename_: return 0 + if self.has_filename_ and self.filename_ != x.filename_: return 0 + if self.has_data_ != x.has_data_: return 0 + if self.has_data_ and self.data_ != x.data_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_filename_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: filename not set.') + if (not self.has_data_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: data not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.filename_)) + n += self.lengthString(len(self.data_)) + return n + 2 + + def Clear(self): + self.clear_filename() + self.clear_data() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.filename_) + out.putVarInt32(18) + out.putPrefixedString(self.data_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_filename(d.getPrefixedString()) + continue + if tt == 18: + self.set_data(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_filename_: res+=prefix+("FileName: %s\n" % self.DebugFormatString(self.filename_)) + if self.has_data_: res+=prefix+("Data: %s\n" % self.DebugFormatString(self.data_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kFileName = 1 + kData = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "FileName", + 2: "Data", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MailMessage(ProtocolBuffer.ProtocolMessage): + has_sender_ = 0 + sender_ = "" + has_replyto_ = 0 + replyto_ = "" + has_subject_ = 0 + subject_ = "" + has_textbody_ = 0 + textbody_ = "" + has_htmlbody_ = 0 + htmlbody_ = "" + + def __init__(self, contents=None): + self.to_ = [] + self.cc_ = [] + self.bcc_ = [] + self.attachment_ = [] + if contents is not None: self.MergeFromString(contents) + + def sender(self): return self.sender_ + + def set_sender(self, x): + self.has_sender_ = 1 + self.sender_ = x + + def clear_sender(self): + if self.has_sender_: + self.has_sender_ = 0 + self.sender_ = "" + + def has_sender(self): return self.has_sender_ + + def replyto(self): return self.replyto_ + + def set_replyto(self, x): + self.has_replyto_ = 1 + self.replyto_ = x + + def clear_replyto(self): + if self.has_replyto_: + self.has_replyto_ = 0 + self.replyto_ = "" + + def has_replyto(self): return self.has_replyto_ + + def to_size(self): return len(self.to_) + def to_list(self): return self.to_ + + def to(self, i): + return self.to_[i] + + def set_to(self, i, x): + self.to_[i] = x + + def add_to(self, x): + self.to_.append(x) + + def clear_to(self): + self.to_ = [] + + def cc_size(self): return len(self.cc_) + def cc_list(self): return self.cc_ + + def cc(self, i): + return self.cc_[i] + + def set_cc(self, i, x): + self.cc_[i] = x + + def add_cc(self, x): + self.cc_.append(x) + + def clear_cc(self): + self.cc_ = [] + + def bcc_size(self): return len(self.bcc_) + def bcc_list(self): return self.bcc_ + + def bcc(self, i): + return self.bcc_[i] + + def set_bcc(self, i, x): + self.bcc_[i] = x + + def add_bcc(self, x): + self.bcc_.append(x) + + def clear_bcc(self): + self.bcc_ = [] + + def subject(self): return self.subject_ + + def set_subject(self, x): + self.has_subject_ = 1 + self.subject_ = x + + def clear_subject(self): + if self.has_subject_: + self.has_subject_ = 0 + self.subject_ = "" + + def has_subject(self): return self.has_subject_ + + def textbody(self): return self.textbody_ + + def set_textbody(self, x): + self.has_textbody_ = 1 + self.textbody_ = x + + def clear_textbody(self): + if self.has_textbody_: + self.has_textbody_ = 0 + self.textbody_ = "" + + def has_textbody(self): return self.has_textbody_ + + def htmlbody(self): return self.htmlbody_ + + def set_htmlbody(self, x): + self.has_htmlbody_ = 1 + self.htmlbody_ = x + + def clear_htmlbody(self): + if self.has_htmlbody_: + self.has_htmlbody_ = 0 + self.htmlbody_ = "" + + def has_htmlbody(self): return self.has_htmlbody_ + + def attachment_size(self): return len(self.attachment_) + def attachment_list(self): return self.attachment_ + + def attachment(self, i): + return self.attachment_[i] + + def mutable_attachment(self, i): + return self.attachment_[i] + + def add_attachment(self): + x = MailAttachment() + self.attachment_.append(x) + return x + + def clear_attachment(self): + self.attachment_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_sender()): self.set_sender(x.sender()) + if (x.has_replyto()): self.set_replyto(x.replyto()) + for i in xrange(x.to_size()): self.add_to(x.to(i)) + for i in xrange(x.cc_size()): self.add_cc(x.cc(i)) + for i in xrange(x.bcc_size()): self.add_bcc(x.bcc(i)) + if (x.has_subject()): self.set_subject(x.subject()) + if (x.has_textbody()): self.set_textbody(x.textbody()) + if (x.has_htmlbody()): self.set_htmlbody(x.htmlbody()) + for i in xrange(x.attachment_size()): self.add_attachment().CopyFrom(x.attachment(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_sender_ != x.has_sender_: return 0 + if self.has_sender_ and self.sender_ != x.sender_: return 0 + if self.has_replyto_ != x.has_replyto_: return 0 + if self.has_replyto_ and self.replyto_ != x.replyto_: return 0 + if len(self.to_) != len(x.to_): return 0 + for e1, e2 in zip(self.to_, x.to_): + if e1 != e2: return 0 + if len(self.cc_) != len(x.cc_): return 0 + for e1, e2 in zip(self.cc_, x.cc_): + if e1 != e2: return 0 + if len(self.bcc_) != len(x.bcc_): return 0 + for e1, e2 in zip(self.bcc_, x.bcc_): + if e1 != e2: return 0 + if self.has_subject_ != x.has_subject_: return 0 + if self.has_subject_ and self.subject_ != x.subject_: return 0 + if self.has_textbody_ != x.has_textbody_: return 0 + if self.has_textbody_ and self.textbody_ != x.textbody_: return 0 + if self.has_htmlbody_ != x.has_htmlbody_: return 0 + if self.has_htmlbody_ and self.htmlbody_ != x.htmlbody_: return 0 + if len(self.attachment_) != len(x.attachment_): return 0 + for e1, e2 in zip(self.attachment_, x.attachment_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_sender_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: sender not set.') + if (not self.has_subject_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: subject not set.') + for p in self.attachment_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.sender_)) + if (self.has_replyto_): n += 1 + self.lengthString(len(self.replyto_)) + n += 1 * len(self.to_) + for i in xrange(len(self.to_)): n += self.lengthString(len(self.to_[i])) + n += 1 * len(self.cc_) + for i in xrange(len(self.cc_)): n += self.lengthString(len(self.cc_[i])) + n += 1 * len(self.bcc_) + for i in xrange(len(self.bcc_)): n += self.lengthString(len(self.bcc_[i])) + n += self.lengthString(len(self.subject_)) + if (self.has_textbody_): n += 1 + self.lengthString(len(self.textbody_)) + if (self.has_htmlbody_): n += 1 + self.lengthString(len(self.htmlbody_)) + n += 1 * len(self.attachment_) + for i in xrange(len(self.attachment_)): n += self.lengthString(self.attachment_[i].ByteSize()) + return n + 2 + + def Clear(self): + self.clear_sender() + self.clear_replyto() + self.clear_to() + self.clear_cc() + self.clear_bcc() + self.clear_subject() + self.clear_textbody() + self.clear_htmlbody() + self.clear_attachment() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.sender_) + if (self.has_replyto_): + out.putVarInt32(18) + out.putPrefixedString(self.replyto_) + for i in xrange(len(self.to_)): + out.putVarInt32(26) + out.putPrefixedString(self.to_[i]) + for i in xrange(len(self.cc_)): + out.putVarInt32(34) + out.putPrefixedString(self.cc_[i]) + for i in xrange(len(self.bcc_)): + out.putVarInt32(42) + out.putPrefixedString(self.bcc_[i]) + out.putVarInt32(50) + out.putPrefixedString(self.subject_) + if (self.has_textbody_): + out.putVarInt32(58) + out.putPrefixedString(self.textbody_) + if (self.has_htmlbody_): + out.putVarInt32(66) + out.putPrefixedString(self.htmlbody_) + for i in xrange(len(self.attachment_)): + out.putVarInt32(74) + out.putVarInt32(self.attachment_[i].ByteSize()) + self.attachment_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_sender(d.getPrefixedString()) + continue + if tt == 18: + self.set_replyto(d.getPrefixedString()) + continue + if tt == 26: + self.add_to(d.getPrefixedString()) + continue + if tt == 34: + self.add_cc(d.getPrefixedString()) + continue + if tt == 42: + self.add_bcc(d.getPrefixedString()) + continue + if tt == 50: + self.set_subject(d.getPrefixedString()) + continue + if tt == 58: + self.set_textbody(d.getPrefixedString()) + continue + if tt == 66: + self.set_htmlbody(d.getPrefixedString()) + continue + if tt == 74: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_attachment().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_sender_: res+=prefix+("Sender: %s\n" % self.DebugFormatString(self.sender_)) + if self.has_replyto_: res+=prefix+("ReplyTo: %s\n" % self.DebugFormatString(self.replyto_)) + cnt=0 + for e in self.to_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("To%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + cnt=0 + for e in self.cc_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Cc%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + cnt=0 + for e in self.bcc_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Bcc%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_subject_: res+=prefix+("Subject: %s\n" % self.DebugFormatString(self.subject_)) + if self.has_textbody_: res+=prefix+("TextBody: %s\n" % self.DebugFormatString(self.textbody_)) + if self.has_htmlbody_: res+=prefix+("HtmlBody: %s\n" % self.DebugFormatString(self.htmlbody_)) + cnt=0 + for e in self.attachment_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Attachment%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kSender = 1 + kReplyTo = 2 + kTo = 3 + kCc = 4 + kBcc = 5 + kSubject = 6 + kTextBody = 7 + kHtmlBody = 8 + kAttachment = 9 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Sender", + 2: "ReplyTo", + 3: "To", + 4: "Cc", + 5: "Bcc", + 6: "Subject", + 7: "TextBody", + 8: "HtmlBody", + 9: "Attachment", + }, 9) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + 7: ProtocolBuffer.Encoder.STRING, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.STRING, + }, 9, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['MailServiceError','MailAttachment','MailMessage'] diff --git a/google_appengine/google/appengine/api/mail_service_pb.pyc b/google_appengine/google/appengine/api/mail_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f408ccfde282ccdeb0bb66aa2cf3910c45280b6b GIT binary patch literal 26633 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/mail_stub.py b/google_appengine/google/appengine/api/mail_stub.py new file mode 100755 index 0000000..151ea76 --- /dev/null +++ b/google_appengine/google/appengine/api/mail_stub.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the mail API, writes email to logs and can optionally +send real email via SMTP or sendmail.""" + + + + + +from email import MIMEBase +from email import MIMEMultipart +from email import MIMEText +import logging +import mail +import mimetypes +import subprocess +import smtplib + +from google.appengine.api import apiproxy_stub + + +class MailServiceStub(apiproxy_stub.APIProxyStub): + """Python only mail service stub. + + This stub does not actually attempt to send email. instead it merely logs + a description of the email to the developers console. + + Args: + host: Host of SMTP server to use. Blank disables sending SMTP. + port: Port of SMTP server to use. + user: User to log in to SMTP server as. + password: Password for SMTP server user. + """ + + def __init__(self, + host=None, + port=25, + user='', + password='', + enable_sendmail=False, + show_mail_body=False, + service_name='mail'): + """Constructor. + + Args: + host: Host of SMTP mail server. + post: Port of SMTP mail server. + user: Sending user of SMTP mail. + password: SMTP password. + enable_sendmail: Whether sendmail enabled or not. + show_mail_body: Whether to show mail body in log. + service_name: Service name expected for all calls. + """ + super(MailServiceStub, self).__init__(service_name) + self._smtp_host = host + self._smtp_port = port + self._smtp_user = user + self._smtp_password = password + self._enable_sendmail = enable_sendmail + self._show_mail_body = show_mail_body + + def _GenerateLog(self, method, message, log): + """Generate a list of log messages representing sent mail. + + Args: + message: Message to write to log. + log: Log function of type string -> None + """ + log('MailService.%s' % method) + log(' From: %s' % message.sender()) + + for address in message.to_list(): + log(' To: %s' % address) + for address in message.cc_list(): + log(' Cc: %s' % address) + for address in message.bcc_list(): + log(' Bcc: %s' % address) + + if message.replyto(): + log(' Reply-to: %s' % message.replyto()) + + log(' Subject: %s' % message.subject()) + + if message.has_textbody(): + log(' Body:') + log(' Content-type: text/plain') + log(' Data length: %d' % len(message.textbody())) + if self._show_mail_body: + log('-----\n' + message.textbody() + '\n-----') + + if message.has_htmlbody(): + log(' Body:') + log(' Content-type: text/html') + log(' Data length: %d' % len(message.htmlbody())) + if self._show_mail_body: + log('-----\n' + message.htmlbody() + '\n-----') + + for attachment in message.attachment_list(): + log(' Attachment:') + log(' File name: %s' % attachment.filename()) + log(' Data length: %s' % len(attachment.data())) + + def _SendSMTP(self, mime_message, smtp_lib=smtplib.SMTP): + """Send MIME message via SMTP. + + Connects to SMTP server and sends MIME message. If user is supplied + will try to login to that server to send as authenticated. Does not + currently support encryption. + + Args: + mime_message: MimeMessage to send. Create using ToMIMEMessage. + smtp_lib: Class of SMTP library. Used for dependency injection. + """ + smtp = smtp_lib() + try: + smtp.connect(self._smtp_host, self._smtp_port) + if self._smtp_user: + smtp.login(self._smtp_user, self._smtp_password) + + tos = ', '.join([mime_message[to] for to in ['To', 'Cc', 'Bcc'] + if mime_message[to]]) + smtp.sendmail(mime_message['From'], tos, str(mime_message)) + finally: + smtp.quit() + + def _SendSendmail(self, mime_message, + popen=subprocess.Popen, + sendmail_command='sendmail'): + """Send MIME message via sendmail, if exists on computer. + + Attempts to send email via sendmail. Any IO failure, including + the program not being found is ignored. + + Args: + mime_message: MimeMessage to send. Create using ToMIMEMessage. + popen: popen function to create a new sub-process. + """ + try: + tos = [mime_message[to] for to in ['To', 'Cc', 'Bcc'] if mime_message[to]] + sendmail_command = '%s %s' % (sendmail_command, ' '.join(tos)) + + try: + child = popen(sendmail_command, + shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + except (IOError, OSError), e: + logging.error('Unable to open pipe to sendmail') + raise + try: + child.stdin.write(str(mime_message)) + child.stdin.close() + finally: + while child.poll() is None: + child.stdout.read(100) + child.stdout.close() + except (IOError, OSError), e: + logging.error('Error sending mail using sendmail: ' + str(e)) + + def _Send(self, request, response, log=logging.info, + smtp_lib=smtplib.SMTP, + popen=subprocess.Popen, + sendmail_command='sendmail'): + """Implementation of MailServer::Send(). + + Logs email message. Contents of attachments are not shown, only + their sizes. If SMTP is configured, will send via SMTP, else + will use Sendmail if it is installed. + + Args: + request: The message to send, a SendMailRequest. + response: The send response, a SendMailResponse. + log: Log function to send log information. Used for dependency + injection. + smtp_lib: Class of SMTP library. Used for dependency injection. + popen2: popen2 function to use for opening pipe to other process. + Used for dependency injection. + """ + self._GenerateLog('Send', request, log) + + if self._smtp_host and self._enable_sendmail: + log('Both SMTP and sendmail are enabled. Ignoring sendmail.') + + import email + + mime_message = mail.MailMessageToMIMEMessage(request) + if self._smtp_host: + self._SendSMTP(mime_message, smtp_lib) + elif self._enable_sendmail: + self._SendSendmail(mime_message, popen, sendmail_command) + else: + logging.info('You are not currently sending out real email. ' + 'If you have sendmail installed you can use it ' + 'by using the server with --enable_sendmail') + + _Dynamic_Send = _Send + + def _SendToAdmins(self, request, response, log=logging.info): + """Implementation of MailServer::SendToAdmins(). + + Logs email message. Contents of attachments are not shown, only + their sizes. + + Given the difficulty of determining who the actual sender + is, Sendmail and SMTP are disabled for this action. + + Args: + request: The message to send, a SendMailRequest. + response: The send response, a SendMailResponse. + log: Log function to send log information. Used for dependency + injection. + """ + self._GenerateLog('SendToAdmins', request, log) + + if self._smtp_host and self._enable_sendmail: + log('Both SMTP and sendmail are enabled. Ignoring sendmail.') + + _Dynamic_SendToAdmins = _SendToAdmins diff --git a/google_appengine/google/appengine/api/mail_stub.pyc b/google_appengine/google/appengine/api/mail_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..864d6e665ebd0a8a73f1763a191d34aad15733a5 GIT binary patch literal 7996 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 MAX_KEY_SIZE: + server_key = sha.new(server_key).hexdigest() + + if server_to_user_dict is not None: + if not isinstance(server_to_user_dict, dict): + raise TypeError('server_to_user_dict must be a dict instance, ' + + 'received %r' % key) + server_to_user_dict[server_key] = key + + return server_key + + +def _validate_encode_value(value, do_pickle): + """Utility function to validate and encode server keys and values. + + Args: + value: Value to store in memcache. If it's a string, it will get passed + along as-is. If it's a unicode string, it will be marked appropriately, + such that retrievals will yield a unicode value. If it's any other data + type, this function will attempt to pickle the data and then store the + serialized result, unpickling it upon retrieval. + do_pickle: Callable that takes an object and returns a non-unicode + string containing the pickled object. + + Returns: + Tuple (stored_value, flags) where: + stored_value: The value as a non-unicode string that should be stored + in memcache. + flags: An integer with bits set from the FLAG_* constants in this file + to indicate the encoding of the key and value. + + Raises: + ValueError: If the encoded value is too large. + pickle.PicklingError: If the value is not a string and could not be pickled. + RuntimeError: If a complicated data structure could not be pickled due to + too many levels of recursion in its composition. + """ + flags = 0 + stored_value = value + + if isinstance(value, str): + pass + elif isinstance(value, unicode): + stored_value = value.encode('utf-8') + flags |= TYPE_UNICODE + elif isinstance(value, bool): + stored_value = str(int(value)) + flags |= TYPE_BOOL + elif isinstance(value, int): + stored_value = str(value) + flags |= TYPE_INT + elif isinstance(value, long): + stored_value = str(value) + flags |= TYPE_LONG + else: + stored_value = do_pickle(value) + flags |= TYPE_PICKLED + + + if len(stored_value) > MAX_VALUE_SIZE: + raise ValueError('Values may not be more than %d bytes in length; ' + 'received %d bytes' % (MAX_VALUE_SIZE, len(stored_value))) + + return (stored_value, flags) + + +def _decode_value(stored_value, flags, do_unpickle): + """Utility function for decoding values retrieved from memcache. + + Args: + stored_value: The value as a non-unicode string that was stored. + flags: An integer with bits set from the FLAG_* constants in this file + that indicate the encoding of the key and value. + do_unpickle: Callable that takes a non-unicode string object that contains + a pickled object and returns the pickled object. + + Returns: + The original object that was stored, be it a normal string, a unicode + string, int, long, or a Python object that was pickled. + + Raises: + pickle.UnpicklingError: If the value could not be unpickled. + """ + assert isinstance(stored_value, str) + assert isinstance(flags, (int, long)) + + type_number = flags & FLAG_TYPE_MASK + value = stored_value + + + if type_number == TYPE_STR: + return value + elif type_number == TYPE_UNICODE: + return value.decode('utf-8') + elif type_number == TYPE_PICKLED: + return do_unpickle(value) + elif type_number == TYPE_BOOL: + return bool(int(value)) + elif type_number == TYPE_INT: + return int(value) + elif type_number == TYPE_LONG: + return long(value) + else: + assert False, "Unknown stored type" + assert False, "Shouldn't get here." + + +class Client(object): + """Memcache client object, through which one invokes all memcache operations. + + Several methods are no-ops to retain source-level compatibility + with the existing popular Python memcache library. + + Any method that takes a 'key' argument will accept that key as a string + (unicode or not) or a tuple of (hash_value, string) where the hash_value, + normally used for sharding onto a memcache instance, is instead ignored, as + Google App Engine deals with the sharding transparently. Keys in memcache are + just bytes, without a specified encoding. All such methods may raise TypeError + if provided a bogus key value and a ValueError if the key is too large. + + Any method that takes a 'value' argument will accept as that value any + string (unicode or not), int, long, or pickle-able Python object, including + all native types. You'll get back from the cache the same type that you + originally put in. + """ + + def __init__(self, servers=None, debug=0, + pickleProtocol=pickle.HIGHEST_PROTOCOL, + pickler=pickle.Pickler, + unpickler=pickle.Unpickler, + pload=None, + pid=None, + make_sync_call=apiproxy_stub_map.MakeSyncCall): + """Create a new Client object. + + No parameters are required. + + Arguments: + servers: Ignored; only for compatibility. + debug: Ignored; only for compatibility. + pickleProtocol: Pickle protocol to use for pickling the object. + pickler: pickle.Pickler sub-class to use for pickling. + unpickler: pickle.Unpickler sub-class to use for unpickling. + pload: Callable to use for retrieving objects by persistent id. + pid: Callable to use for determine the persistent id for objects, if any. + make_sync_call: Function to use to make an App Engine service call. + Used for testing. + """ + self._pickle_data = cStringIO.StringIO() + self._pickler_instance = pickler(self._pickle_data, + protocol=pickleProtocol) + self._unpickler_instance = unpickler(self._pickle_data) + if pid is not None: + self._pickler_instance.persistent_id = pid + if pload is not None: + self._unpickler_instance.persistent_load = pload + + def DoPickle(value): + self._pickle_data.truncate(0) + self._pickler_instance.clear_memo() + self._pickler_instance.dump(value) + return self._pickle_data.getvalue() + self._do_pickle = DoPickle + + def DoUnpickle(value): + self._pickle_data.truncate(0) + self._pickle_data.write(value) + self._pickle_data.seek(0) + self._unpickler_instance.memo.clear() + return self._unpickler_instance.load() + self._do_unpickle = DoUnpickle + + self._make_sync_call = make_sync_call + + def set_servers(self, servers): + """Sets the pool of memcache servers used by the client. + + This is purely a compatibility method. In Google App Engine, it's a no-op. + """ + pass + + def disconnect_all(self): + """Closes all connections to memcache servers. + + This is purely a compatibility method. In Google App Engine, it's a no-op. + """ + pass + + def forget_dead_hosts(self): + """Resets all servers to the alive status. + + This is purely a compatibility method. In Google App Engine, it's a no-op. + """ + pass + + def debuglog(self): + """Logging function for debugging information. + + This is purely a compatibility method. In Google App Engine, it's a no-op. + """ + pass + + def get_stats(self): + """Gets memcache statistics for this application. + + All of these statistics may reset due to various transient conditions. They + provide the best information available at the time of being called. + + Returns: + Dictionary mapping statistic names to associated values. Statistics and + their associated meanings: + + hits: Number of cache get requests resulting in a cache hit. + misses: Number of cache get requests resulting in a cache miss. + byte_hits: Sum of bytes transferred on get requests. Rolls over to + zero on overflow. + items: Number of key/value pairs in the cache. + bytes: Total size of all items in the cache. + oldest_item_age: How long in seconds since the oldest item in the + cache was accessed. Effectively, this indicates how long a new + item will survive in the cache without being accessed. This is + _not_ the amount of time that has elapsed since the item was + created. + + On error, returns None. + """ + request = MemcacheStatsRequest() + response = MemcacheStatsResponse() + try: + self._make_sync_call('memcache', 'Stats', request, response) + except apiproxy_errors.Error: + return None + + if not response.has_stats(): + return { + STAT_HITS: 0, + STAT_MISSES: 0, + STAT_BYTE_HITS: 0, + STAT_ITEMS: 0, + STAT_BYTES: 0, + STAT_OLDEST_ITEM_AGES: 0, + } + + stats = response.stats() + return { + STAT_HITS: stats.hits(), + STAT_MISSES: stats.misses(), + STAT_BYTE_HITS: stats.byte_hits(), + STAT_ITEMS: stats.items(), + STAT_BYTES: stats.bytes(), + STAT_OLDEST_ITEM_AGES: stats.oldest_item_age(), + } + + def flush_all(self): + """Deletes everything in memcache. + + Returns: + True on success, False on RPC or server error. + """ + request = MemcacheFlushRequest() + response = MemcacheFlushResponse() + try: + self._make_sync_call('memcache', 'FlushAll', request, response) + except apiproxy_errors.Error: + return False + return True + + def get(self, key, namespace=None): + """Looks up a single key in memcache. + + If you have multiple items to load, though, it's much more efficient + to use get_multi() instead, which loads them in one bulk operation, + reducing the networking latency that'd otherwise be required to do + many serialized get() operations. + + Args: + key: The key in memcache to look up. See docs on Client + for details of format. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + The value of the key, if found in memcache, else None. + """ + request = MemcacheGetRequest() + request.add_key(_key_string(key)) + _add_name_space(request, namespace) + response = MemcacheGetResponse() + try: + self._make_sync_call('memcache', 'Get', request, response) + except apiproxy_errors.Error: + return None + + if not response.item_size(): + return None + + return _decode_value(response.item(0).value(), + response.item(0).flags(), + self._do_unpickle) + + def get_multi(self, keys, key_prefix='', namespace=None): + """Looks up multiple keys from memcache in one operation. + + This is the recommended way to do bulk loads. + + Args: + keys: List of keys to look up. Keys may be strings or + tuples of (hash_value, string). Google App Engine + does the sharding and hashing automatically, though, so the hash + value is ignored. To memcache, keys are just series of bytes, + and not in any particular encoding. + key_prefix: Prefix to prepend to all keys when talking to the server; + not included in the returned dictionary. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + A dictionary of the keys and values that were present in memcache. + Even if the key_prefix was specified, that key_prefix won't be on + the keys in the returned dictionary. + """ + request = MemcacheGetRequest() + _add_name_space(request, namespace) + response = MemcacheGetResponse() + user_key = {} + for key in keys: + request.add_key(_key_string(key, key_prefix, user_key)) + try: + self._make_sync_call('memcache', 'Get', request, response) + except apiproxy_errors.Error: + return {} + + return_value = {} + for returned_item in response.item_list(): + value = _decode_value(returned_item.value(), returned_item.flags(), + self._do_unpickle) + return_value[user_key[returned_item.key()]] = value + return return_value + + def delete(self, key, seconds=0, namespace=None): + """Deletes a key from memcache. + + Args: + key: Key to delete. See docs on Client for detils. + seconds: Optional number of seconds to make deleted items 'locked' + for 'add' operations. Value can be a delta from current time (up to + 1 month), or an absolute Unix epoch time. Defaults to 0, which means + items can be immediately added. With or without this option, + a 'set' operation will always work. Float values will be rounded up to + the nearest whole second. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + DELETE_NETWORK_FAILURE (0) on network failure, + DELETE_ITEM_MISSING (1) if the server tried to delete the item but + didn't have it, or + DELETE_SUCCESSFUL (2) if the item was actually deleted. + This can be used as a boolean value, where a network failure is the + only bad condition. + """ + if not isinstance(seconds, (int, long, float)): + raise TypeError('Delete timeout must be a number.') + if seconds < 0: + raise ValueError('Delete timeout must be non-negative.') + + request = MemcacheDeleteRequest() + _add_name_space(request, namespace) + response = MemcacheDeleteResponse() + + delete_item = request.add_item() + delete_item.set_key(_key_string(key)) + delete_item.set_delete_time(int(math.ceil(seconds))) + try: + self._make_sync_call('memcache', 'Delete', request, response) + except apiproxy_errors.Error: + return DELETE_NETWORK_FAILURE + assert response.delete_status_size() == 1, 'Unexpected status size.' + + if response.delete_status(0) == MemcacheDeleteResponse.DELETED: + return DELETE_SUCCESSFUL + elif response.delete_status(0) == MemcacheDeleteResponse.NOT_FOUND: + return DELETE_ITEM_MISSING + assert False, 'Unexpected deletion status code.' + + def delete_multi(self, keys, seconds=0, key_prefix='', namespace=None): + """Delete multiple keys at once. + + Args: + keys: List of keys to delete. + seconds: Optional number of seconds to make deleted items 'locked' + for 'add' operations. Value can be a delta from current time (up to + 1 month), or an absolute Unix epoch time. Defaults to 0, which means + items can be immediately added. With or without this option, + a 'set' operation will always work. Float values will be rounded up to + the nearest whole second. + key_prefix: Prefix to put on all keys when sending specified + keys to memcache. See docs for get_multi() and set_multi(). + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + True if all operations completed successfully. False if one + or more failed to complete. + """ + if not isinstance(seconds, (int, long, float)): + raise TypeError('Delete timeout must be a number.') + if seconds < 0: + raise ValueError('Delete timeout must not be negative.') + + request = MemcacheDeleteRequest() + _add_name_space(request, namespace) + response = MemcacheDeleteResponse() + + for key in keys: + delete_item = request.add_item() + delete_item.set_key(_key_string(key, key_prefix=key_prefix)) + delete_item.set_delete_time(int(math.ceil(seconds))) + try: + self._make_sync_call('memcache', 'Delete', request, response) + except apiproxy_errors.Error: + return False + return True + + def set(self, key, value, time=0, min_compress_len=0, namespace=None): + """Sets a key's value, regardless of previous contents in cache. + + Unlike add() and replace(), this method always sets (or + overwrites) the value in memcache, regardless of previous + contents. + + Args: + key: Key to set. See docs on Client for details. + value: Value to set. Any type. If complex, will be pickled. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + min_compress_len: Ignored option for compatibility. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + True if set. False on error. + """ + return self._set_with_policy(MemcacheSetRequest.SET, key, value, time=time, + namespace=namespace) + + def add(self, key, value, time=0, min_compress_len=0, namespace=None): + """Sets a key's value, iff item is not already in memcache. + + Args: + key: Key to set. See docs on Client for details. + value: Value to set. Any type. If complex, will be pickled. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + min_compress_len: Ignored option for compatibility. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + True if added. False on error. + """ + return self._set_with_policy(MemcacheSetRequest.ADD, key, value, time=time, + namespace=namespace) + + def replace(self, key, value, time=0, min_compress_len=0, namespace=None): + """Replaces a key's value, failing if item isn't already in memcache. + + Args: + key: Key to set. See docs on Client for details. + value: Value to set. Any type. If complex, will be pickled. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + min_compress_len: Ignored option for compatibility. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + True if replaced. False on RPC error or cache miss. + """ + return self._set_with_policy(MemcacheSetRequest.REPLACE, + key, value, time=time, namespace=namespace) + + def _set_with_policy(self, policy, key, value, time=0, namespace=None): + """Sets a single key with a specified policy. + + Helper function for set(), add(), and replace(). + + Args: + policy: One of MemcacheSetRequest.SET, .ADD, or .REPLACE. + key: Key to add, set, or replace. See docs on Client for details. + value: Value to set. + time: Expiration time, defaulting to 0 (never expiring). + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + True if stored, False on RPC error or policy error, e.g. a replace + that failed due to the item not already existing, or an add + failing due to the item not already existing. + """ + if not isinstance(time, (int, long, float)): + raise TypeError('Expiration must be a number.') + if time < 0: + raise ValueError('Expiration must not be negative.') + + request = MemcacheSetRequest() + item = request.add_item() + item.set_key(_key_string(key)) + stored_value, flags = _validate_encode_value(value, self._do_pickle) + item.set_value(stored_value) + item.set_flags(flags) + item.set_set_policy(policy) + item.set_expiration_time(int(math.ceil(time))) + _add_name_space(request, namespace) + response = MemcacheSetResponse() + try: + self._make_sync_call('memcache', 'Set', request, response) + except apiproxy_errors.Error: + return False + if response.set_status_size() != 1: + return False + return response.set_status(0) == MemcacheSetResponse.STORED + + def _set_multi_with_policy(self, policy, mapping, time=0, key_prefix='', + namespace=None): + """Set multiple keys with a specified policy. + + Helper function for set_multi(), add_multi(), and replace_multi(). This + reduces the network latency of doing many requests in serial. + + Args: + policy: One of MemcacheSetRequest.SET, ADD, or REPLACE. + mapping: Dictionary of keys to values. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + key_prefix: Prefix for to prepend to all keys. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + A list of keys whose values were NOT set. On total success, + this list should be empty. On network/RPC/server errors, + a list of all input keys is returned; in this case the keys + may or may not have been updated. + """ + if not isinstance(time, (int, long, float)): + raise TypeError('Expiration must be a number.') + if time < 0.0: + raise ValueError('Expiration must not be negative.') + + request = MemcacheSetRequest() + _add_name_space(request, namespace) + user_key = {} + server_keys = [] + for key, value in mapping.iteritems(): + server_key = _key_string(key, key_prefix, user_key) + stored_value, flags = _validate_encode_value(value, self._do_pickle) + server_keys.append(server_key) + + item = request.add_item() + item.set_key(server_key) + item.set_value(stored_value) + item.set_flags(flags) + item.set_set_policy(policy) + item.set_expiration_time(int(math.ceil(time))) + + response = MemcacheSetResponse() + try: + self._make_sync_call('memcache', 'Set', request, response) + except apiproxy_errors.Error: + return user_key.values() + + assert response.set_status_size() == len(server_keys) + + unset_list = [] + for server_key, set_status in zip(server_keys, response.set_status_list()): + if set_status != MemcacheSetResponse.STORED: + unset_list.append(user_key[server_key]) + + return unset_list + + def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0, + namespace=None): + """Set multiple keys' values at once, regardless of previous contents. + + Args: + mapping: Dictionary of keys to values. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + key_prefix: Prefix for to prepend to all keys. + min_compress_len: Unimplemented compatibility option. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + A list of keys whose values were NOT set. On total success, + this list should be empty. + """ + return self._set_multi_with_policy(MemcacheSetRequest.SET, mapping, + time=time, key_prefix=key_prefix, + namespace=namespace) + + def add_multi(self, mapping, time=0, key_prefix='', min_compress_len=0, + namespace=None): + """Set multiple keys' values iff items are not already in memcache. + + Args: + mapping: Dictionary of keys to values. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + key_prefix: Prefix for to prepend to all keys. + min_compress_len: Unimplemented compatibility option. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + A list of keys whose values were NOT set because they did not already + exist in memcache. On total success, this list should be empty. + """ + return self._set_multi_with_policy(MemcacheSetRequest.ADD, mapping, + time=time, key_prefix=key_prefix, + namespace=namespace) + + def replace_multi(self, mapping, time=0, key_prefix='', min_compress_len=0, + namespace=None): + """Replace multiple keys' values, failing if the items aren't in memcache. + + Args: + mapping: Dictionary of keys to values. + time: Optional expiration time, either relative number of seconds + from current time (up to 1 month), or an absolute Unix epoch time. + By default, items never expire, though items may be evicted due to + memory pressure. Float values will be rounded up to the nearest + whole second. + key_prefix: Prefix for to prepend to all keys. + min_compress_len: Unimplemented compatibility option. + namespace: a string specifying an optional namespace to use in + the request. + + Returns: + A list of keys whose values were NOT set because they already existed + in memcache. On total success, this list should be empty. + """ + return self._set_multi_with_policy(MemcacheSetRequest.REPLACE, mapping, + time=time, key_prefix=key_prefix, + namespace=namespace) + + def incr(self, key, delta=1, namespace=None, initial_value=None): + """Atomically increments a key's value. + + Internally, the value is a unsigned 64-bit integer. Memcache + doesn't check 64-bit overflows. The value, if too large, will + wrap around. + + Unless an initial_value is specified, the key must already exist + in the cache to be incremented. To initialize a counter, either + specify initial_value or set() it to the initial value, as an + ASCII decimal integer. Future get()s of the key, post-increment, + will still be an ASCII decimal value. + + Args: + key: Key to increment. If an iterable collection, each one of the keys + will be offset. See Client's docstring for details. + delta: Non-negative integer value (int or long) to increment key by, + defaulting to 1. + namespace: a string specifying an optional namespace to use in + the request. + initial_value: initial value to put in the cache, if it doesn't + already exist. The default value, None, will not create a cache + entry if it doesn't already exist. + + Returns: + If key was a single value, the new long integer value, or None if key + was not in the cache, could not be incremented for any other reason, or + a network/RPC/server error occurred. + + If key was an iterable collection, a dictionary will be returned + mapping supplied keys to values, with the values having the same meaning + as the singular return value of this method. + + Raises: + ValueError: If number is negative. + TypeError: If delta isn't an int or long. + """ + return self._incrdecr(key, False, delta, namespace=namespace, + initial_value=initial_value) + + def decr(self, key, delta=1, namespace=None, initial_value=None): + """Atomically decrements a key's value. + + Internally, the value is a unsigned 64-bit integer. Memcache + caps decrementing below zero to zero. + + The key must already exist in the cache to be decremented. See + docs on incr() for details. + + Args: + key: Key to decrement. If an iterable collection, each one of the keys + will be offset. See Client's docstring for details. + delta: Non-negative integer value (int or long) to decrement key by, + defaulting to 1. + namespace: a string specifying an optional namespace to use in + the request. + initial_value: initial value to put in the cache, if it doesn't + already exist. The default value, None, will not create a cache + entry if it doesn't already exist. + + Returns: + If key was a single value, the new long integer value, or None if key + was not in the cache, could not be decremented for any other reason, or + a network/RPC/server error occurred. + + If key was an iterable collection, a dictionary will be returned + mapping supplied keys to values, with the values having the same meaning + as the singular return value of this method. + + Raises: + ValueError: If number is negative. + TypeError: If delta isn't an int or long. + """ + return self._incrdecr(key, True, delta, namespace=namespace, + initial_value=initial_value) + + def _incrdecr(self, key, is_negative, delta, namespace=None, + initial_value=None): + """Increment or decrement a key by a provided delta. + + Args: + key: Key to increment or decrement. If an iterable collection, each + one of the keys will be offset. + is_negative: Boolean, if this is a decrement. + delta: Non-negative integer amount (int or long) to increment + or decrement by. + namespace: a string specifying an optional namespace to use in + the request. + initial_value: initial value to put in the cache, if it doesn't + already exist. The default value, None, will not create a cache + entry if it doesn't already exist. + + Returns: + New long integer value, or None on cache miss or network/RPC/server + error. + + Raises: + ValueError: If delta is negative. + TypeError: If delta isn't an int or long. + """ + if not isinstance(delta, (int, long)): + raise TypeError('Delta must be an integer or long, received %r' % delta) + if delta < 0: + raise ValueError('Delta must not be negative.') + + if not isinstance(key, basestring): + try: + iter(key) + if is_negative: + delta = -delta + return self.offset_multi( + dict((k, delta) for k in key), + namespace=namespace, + initial_value=initial_value) + except TypeError: + pass + + request = MemcacheIncrementRequest() + _add_name_space(request, namespace) + response = MemcacheIncrementResponse() + request.set_key(_key_string(key)) + request.set_delta(delta) + if is_negative: + request.set_direction(MemcacheIncrementRequest.DECREMENT) + else: + request.set_direction(MemcacheIncrementRequest.INCREMENT) + if initial_value is not None: + request.set_initial_value(long(initial_value)) + + try: + self._make_sync_call('memcache', 'Increment', request, response) + except apiproxy_errors.Error: + return None + + if response.has_new_value(): + return response.new_value() + return None + + def offset_multi(self, mapping, key_prefix='', + namespace=None, initial_value=None): + """Offsets multiple keys by a delta, incrementing and decrementing in batch. + + Args: + mapping: Dictionary mapping keys to deltas (positive or negative integers) + to apply to each corresponding key. + key_prefix: Prefix for to prepend to all keys. + initial_value: Initial value to put in the cache, if it doesn't + already exist. The default value, None, will not create a cache + entry if it doesn't already exist. + namespace: A string specifying an optional namespace to use in + the request. + + Returns: + Dictionary mapping input keys to new integer values. The new value will + be None if an error occurs, the key does not already exist, or the value + was not an integer type. The values will wrap-around at unsigned 64-bit + integer-maximum and underflow will be floored at zero. + """ + if initial_value is not None: + if not isinstance(initial_value, (int, long)): + raise TypeError('initial_value must be an integer') + if initial_value < 0: + raise ValueError('initial_value must be >= 0') + + request = MemcacheBatchIncrementRequest() + response = MemcacheBatchIncrementResponse() + _add_name_space(request, namespace) + + for key, delta in mapping.iteritems(): + if not isinstance(delta, (int, long)): + raise TypeError('Delta must be an integer or long, received %r' % delta) + if delta >= 0: + direction = MemcacheIncrementRequest.INCREMENT + else: + delta = -delta + direction = MemcacheIncrementRequest.DECREMENT + + server_key = _key_string(key, key_prefix) + + item = request.add_item() + item.set_key(server_key) + item.set_delta(delta) + item.set_direction(direction) + if initial_value is not None: + item.set_initial_value(initial_value) + + try: + self._make_sync_call('memcache', 'BatchIncrement', request, response) + except apiproxy_errors.Error: + return dict((k, None) for k in mapping.iterkeys()) + + assert response.item_size() == len(mapping) + + result_dict = {} + for key, resp_item in zip(mapping.iterkeys(), response.item_list()): + if (resp_item.increment_status() == MemcacheIncrementResponse.OK and + resp_item.has_new_value()): + result_dict[key] = resp_item.new_value() + else: + result_dict[key] = None + + return result_dict + + +_CLIENT = None + + +def setup_client(client_obj): + """Sets the Client object instance to use for all module-level methods. + + Use this method if you want to have customer persistent_id() or + persistent_load() functions associated with your client. + + Args: + client_obj: Instance of the memcache.Client object. + """ + global _CLIENT + var_dict = globals() + + _CLIENT = client_obj + var_dict['set_servers'] = _CLIENT.set_servers + var_dict['disconnect_all'] = _CLIENT.disconnect_all + var_dict['forget_dead_hosts'] = _CLIENT.forget_dead_hosts + var_dict['debuglog'] = _CLIENT.debuglog + var_dict['get'] = _CLIENT.get + var_dict['get_multi'] = _CLIENT.get_multi + var_dict['set'] = _CLIENT.set + var_dict['set_multi'] = _CLIENT.set_multi + var_dict['add'] = _CLIENT.add + var_dict['add_multi'] = _CLIENT.add_multi + var_dict['replace'] = _CLIENT.replace + var_dict['replace_multi'] = _CLIENT.replace_multi + var_dict['delete'] = _CLIENT.delete + var_dict['delete_multi'] = _CLIENT.delete_multi + var_dict['incr'] = _CLIENT.incr + var_dict['decr'] = _CLIENT.decr + var_dict['flush_all'] = _CLIENT.flush_all + var_dict['get_stats'] = _CLIENT.get_stats + var_dict['offset_multi'] = _CLIENT.offset_multi + + +setup_client(Client()) diff --git a/google_appengine/google/appengine/api/memcache/__init__.pyc b/google_appengine/google/appengine/api/memcache/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df2e3da00649a30df821e1b0860d3d715688b9ce GIT binary patch literal 39284 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 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheGetRequest(ProtocolBuffer.ProtocolMessage): + has_name_space_ = 0 + name_space_ = "" + has_for_cas_ = 0 + for_cas_ = 0 + + def __init__(self, contents=None): + self.key_ = [] + if contents is not None: self.MergeFromString(contents) + + def key_size(self): return len(self.key_) + def key_list(self): return self.key_ + + def key(self, i): + return self.key_[i] + + def set_key(self, i, x): + self.key_[i] = x + + def add_key(self, x): + self.key_.append(x) + + def clear_key(self): + self.key_ = [] + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def for_cas(self): return self.for_cas_ + + def set_for_cas(self, x): + self.has_for_cas_ = 1 + self.for_cas_ = x + + def clear_for_cas(self): + if self.has_for_cas_: + self.has_for_cas_ = 0 + self.for_cas_ = 0 + + def has_for_cas(self): return self.has_for_cas_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.key_size()): self.add_key(x.key(i)) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_for_cas()): self.set_for_cas(x.for_cas()) + + def Equals(self, x): + if x is self: return 1 + if len(self.key_) != len(x.key_): return 0 + for e1, e2 in zip(self.key_, x.key_): + if e1 != e2: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_for_cas_ != x.has_for_cas_: return 0 + if self.has_for_cas_ and self.for_cas_ != x.for_cas_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.key_) + for i in xrange(len(self.key_)): n += self.lengthString(len(self.key_[i])) + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + if (self.has_for_cas_): n += 2 + return n + 0 + + def Clear(self): + self.clear_key() + self.clear_name_space() + self.clear_for_cas() + + def OutputUnchecked(self, out): + for i in xrange(len(self.key_)): + out.putVarInt32(10) + out.putPrefixedString(self.key_[i]) + if (self.has_name_space_): + out.putVarInt32(18) + out.putPrefixedString(self.name_space_) + if (self.has_for_cas_): + out.putVarInt32(32) + out.putBoolean(self.for_cas_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_key(d.getPrefixedString()) + continue + if tt == 18: + self.set_name_space(d.getPrefixedString()) + continue + if tt == 32: + self.set_for_cas(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("key%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_for_cas_: res+=prefix+("for_cas: %s\n" % self.DebugFormatBool(self.for_cas_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + kname_space = 2 + kfor_cas = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "name_space", + 4: "for_cas", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheGetResponse_Item(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + has_flags_ = 0 + flags_ = 0 + has_cas_id_ = 0 + cas_id_ = 0 + has_expires_in_seconds_ = 0 + expires_in_seconds_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + def flags(self): return self.flags_ + + def set_flags(self, x): + self.has_flags_ = 1 + self.flags_ = x + + def clear_flags(self): + if self.has_flags_: + self.has_flags_ = 0 + self.flags_ = 0 + + def has_flags(self): return self.has_flags_ + + def cas_id(self): return self.cas_id_ + + def set_cas_id(self, x): + self.has_cas_id_ = 1 + self.cas_id_ = x + + def clear_cas_id(self): + if self.has_cas_id_: + self.has_cas_id_ = 0 + self.cas_id_ = 0 + + def has_cas_id(self): return self.has_cas_id_ + + def expires_in_seconds(self): return self.expires_in_seconds_ + + def set_expires_in_seconds(self, x): + self.has_expires_in_seconds_ = 1 + self.expires_in_seconds_ = x + + def clear_expires_in_seconds(self): + if self.has_expires_in_seconds_: + self.has_expires_in_seconds_ = 0 + self.expires_in_seconds_ = 0 + + def has_expires_in_seconds(self): return self.has_expires_in_seconds_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + if (x.has_flags()): self.set_flags(x.flags()) + if (x.has_cas_id()): self.set_cas_id(x.cas_id()) + if (x.has_expires_in_seconds()): self.set_expires_in_seconds(x.expires_in_seconds()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + if self.has_flags_ != x.has_flags_: return 0 + if self.has_flags_ and self.flags_ != x.flags_: return 0 + if self.has_cas_id_ != x.has_cas_id_: return 0 + if self.has_cas_id_ and self.cas_id_ != x.cas_id_: return 0 + if self.has_expires_in_seconds_ != x.has_expires_in_seconds_: return 0 + if self.has_expires_in_seconds_ and self.expires_in_seconds_ != x.expires_in_seconds_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + if (self.has_flags_): n += 5 + if (self.has_cas_id_): n += 9 + if (self.has_expires_in_seconds_): n += 1 + self.lengthVarInt64(self.expires_in_seconds_) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + self.clear_flags() + self.clear_cas_id() + self.clear_expires_in_seconds() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.key_) + out.putVarInt32(26) + out.putPrefixedString(self.value_) + if (self.has_flags_): + out.putVarInt32(37) + out.put32(self.flags_) + if (self.has_cas_id_): + out.putVarInt32(41) + out.put64(self.cas_id_) + if (self.has_expires_in_seconds_): + out.putVarInt32(48) + out.putVarInt32(self.expires_in_seconds_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_key(d.getPrefixedString()) + continue + if tt == 26: + self.set_value(d.getPrefixedString()) + continue + if tt == 37: + self.set_flags(d.get32()) + continue + if tt == 41: + self.set_cas_id(d.get64()) + continue + if tt == 48: + self.set_expires_in_seconds(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + if self.has_flags_: res+=prefix+("flags: %s\n" % self.DebugFormatFixed32(self.flags_)) + if self.has_cas_id_: res+=prefix+("cas_id: %s\n" % self.DebugFormatFixed64(self.cas_id_)) + if self.has_expires_in_seconds_: res+=prefix+("expires_in_seconds: %s\n" % self.DebugFormatInt32(self.expires_in_seconds_)) + return res + +class MemcacheGetResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheGetResponse_Item() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.item_) + for i in xrange(len(self.item_)): n += self.item_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_item() + + def OutputUnchecked(self, out): + for i in xrange(len(self.item_)): + out.putVarInt32(11) + self.item_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_item().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Item%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kItemGroup = 1 + kItemkey = 2 + kItemvalue = 3 + kItemflags = 4 + kItemcas_id = 5 + kItemexpires_in_seconds = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Item", + 2: "key", + 3: "value", + 4: "flags", + 5: "cas_id", + 6: "expires_in_seconds", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.FLOAT, + 5: ProtocolBuffer.Encoder.DOUBLE, + 6: ProtocolBuffer.Encoder.NUMERIC, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheSetRequest_Item(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + has_flags_ = 0 + flags_ = 0 + has_set_policy_ = 0 + set_policy_ = 1 + has_expiration_time_ = 0 + expiration_time_ = 0 + has_cas_id_ = 0 + cas_id_ = 0 + has_for_cas_ = 0 + for_cas_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + def flags(self): return self.flags_ + + def set_flags(self, x): + self.has_flags_ = 1 + self.flags_ = x + + def clear_flags(self): + if self.has_flags_: + self.has_flags_ = 0 + self.flags_ = 0 + + def has_flags(self): return self.has_flags_ + + def set_policy(self): return self.set_policy_ + + def set_set_policy(self, x): + self.has_set_policy_ = 1 + self.set_policy_ = x + + def clear_set_policy(self): + if self.has_set_policy_: + self.has_set_policy_ = 0 + self.set_policy_ = 1 + + def has_set_policy(self): return self.has_set_policy_ + + def expiration_time(self): return self.expiration_time_ + + def set_expiration_time(self, x): + self.has_expiration_time_ = 1 + self.expiration_time_ = x + + def clear_expiration_time(self): + if self.has_expiration_time_: + self.has_expiration_time_ = 0 + self.expiration_time_ = 0 + + def has_expiration_time(self): return self.has_expiration_time_ + + def cas_id(self): return self.cas_id_ + + def set_cas_id(self, x): + self.has_cas_id_ = 1 + self.cas_id_ = x + + def clear_cas_id(self): + if self.has_cas_id_: + self.has_cas_id_ = 0 + self.cas_id_ = 0 + + def has_cas_id(self): return self.has_cas_id_ + + def for_cas(self): return self.for_cas_ + + def set_for_cas(self, x): + self.has_for_cas_ = 1 + self.for_cas_ = x + + def clear_for_cas(self): + if self.has_for_cas_: + self.has_for_cas_ = 0 + self.for_cas_ = 0 + + def has_for_cas(self): return self.has_for_cas_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + if (x.has_flags()): self.set_flags(x.flags()) + if (x.has_set_policy()): self.set_set_policy(x.set_policy()) + if (x.has_expiration_time()): self.set_expiration_time(x.expiration_time()) + if (x.has_cas_id()): self.set_cas_id(x.cas_id()) + if (x.has_for_cas()): self.set_for_cas(x.for_cas()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + if self.has_flags_ != x.has_flags_: return 0 + if self.has_flags_ and self.flags_ != x.flags_: return 0 + if self.has_set_policy_ != x.has_set_policy_: return 0 + if self.has_set_policy_ and self.set_policy_ != x.set_policy_: return 0 + if self.has_expiration_time_ != x.has_expiration_time_: return 0 + if self.has_expiration_time_ and self.expiration_time_ != x.expiration_time_: return 0 + if self.has_cas_id_ != x.has_cas_id_: return 0 + if self.has_cas_id_ and self.cas_id_ != x.cas_id_: return 0 + if self.has_for_cas_ != x.has_for_cas_: return 0 + if self.has_for_cas_ and self.for_cas_ != x.for_cas_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + if (self.has_flags_): n += 5 + if (self.has_set_policy_): n += 1 + self.lengthVarInt64(self.set_policy_) + if (self.has_expiration_time_): n += 5 + if (self.has_cas_id_): n += 9 + if (self.has_for_cas_): n += 2 + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + self.clear_flags() + self.clear_set_policy() + self.clear_expiration_time() + self.clear_cas_id() + self.clear_for_cas() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.key_) + out.putVarInt32(26) + out.putPrefixedString(self.value_) + if (self.has_flags_): + out.putVarInt32(37) + out.put32(self.flags_) + if (self.has_set_policy_): + out.putVarInt32(40) + out.putVarInt32(self.set_policy_) + if (self.has_expiration_time_): + out.putVarInt32(53) + out.put32(self.expiration_time_) + if (self.has_cas_id_): + out.putVarInt32(65) + out.put64(self.cas_id_) + if (self.has_for_cas_): + out.putVarInt32(72) + out.putBoolean(self.for_cas_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_key(d.getPrefixedString()) + continue + if tt == 26: + self.set_value(d.getPrefixedString()) + continue + if tt == 37: + self.set_flags(d.get32()) + continue + if tt == 40: + self.set_set_policy(d.getVarInt32()) + continue + if tt == 53: + self.set_expiration_time(d.get32()) + continue + if tt == 65: + self.set_cas_id(d.get64()) + continue + if tt == 72: + self.set_for_cas(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + if self.has_flags_: res+=prefix+("flags: %s\n" % self.DebugFormatFixed32(self.flags_)) + if self.has_set_policy_: res+=prefix+("set_policy: %s\n" % self.DebugFormatInt32(self.set_policy_)) + if self.has_expiration_time_: res+=prefix+("expiration_time: %s\n" % self.DebugFormatFixed32(self.expiration_time_)) + if self.has_cas_id_: res+=prefix+("cas_id: %s\n" % self.DebugFormatFixed64(self.cas_id_)) + if self.has_for_cas_: res+=prefix+("for_cas: %s\n" % self.DebugFormatBool(self.for_cas_)) + return res + +class MemcacheSetRequest(ProtocolBuffer.ProtocolMessage): + + SET = 1 + ADD = 2 + REPLACE = 3 + CAS = 4 + + _SetPolicy_NAMES = { + 1: "SET", + 2: "ADD", + 3: "REPLACE", + 4: "CAS", + } + + def SetPolicy_Name(cls, x): return cls._SetPolicy_NAMES.get(x, "") + SetPolicy_Name = classmethod(SetPolicy_Name) + + has_name_space_ = 0 + name_space_ = "" + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheSetRequest_Item() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + if (x.has_name_space()): self.set_name_space(x.name_space()) + + def Equals(self, x): + if x is self: return 1 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.item_) + for i in xrange(len(self.item_)): n += self.item_[i].ByteSize() + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + return n + 0 + + def Clear(self): + self.clear_item() + self.clear_name_space() + + def OutputUnchecked(self, out): + for i in xrange(len(self.item_)): + out.putVarInt32(11) + self.item_[i].OutputUnchecked(out) + out.putVarInt32(12) + if (self.has_name_space_): + out.putVarInt32(58) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_item().TryMerge(d) + continue + if tt == 58: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Item%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kItemGroup = 1 + kItemkey = 2 + kItemvalue = 3 + kItemflags = 4 + kItemset_policy = 5 + kItemexpiration_time = 6 + kItemcas_id = 8 + kItemfor_cas = 9 + kname_space = 7 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Item", + 2: "key", + 3: "value", + 4: "flags", + 5: "set_policy", + 6: "expiration_time", + 7: "name_space", + 8: "cas_id", + 9: "for_cas", + }, 9) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.FLOAT, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.FLOAT, + 7: ProtocolBuffer.Encoder.STRING, + 8: ProtocolBuffer.Encoder.DOUBLE, + 9: ProtocolBuffer.Encoder.NUMERIC, + }, 9, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheSetResponse(ProtocolBuffer.ProtocolMessage): + + STORED = 1 + NOT_STORED = 2 + ERROR = 3 + EXISTS = 4 + + _SetStatusCode_NAMES = { + 1: "STORED", + 2: "NOT_STORED", + 3: "ERROR", + 4: "EXISTS", + } + + def SetStatusCode_Name(cls, x): return cls._SetStatusCode_NAMES.get(x, "") + SetStatusCode_Name = classmethod(SetStatusCode_Name) + + + def __init__(self, contents=None): + self.set_status_ = [] + if contents is not None: self.MergeFromString(contents) + + def set_status_size(self): return len(self.set_status_) + def set_status_list(self): return self.set_status_ + + def set_status(self, i): + return self.set_status_[i] + + def set_set_status(self, i, x): + self.set_status_[i] = x + + def add_set_status(self, x): + self.set_status_.append(x) + + def clear_set_status(self): + self.set_status_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.set_status_size()): self.add_set_status(x.set_status(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.set_status_) != len(x.set_status_): return 0 + for e1, e2 in zip(self.set_status_, x.set_status_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.set_status_) + for i in xrange(len(self.set_status_)): n += self.lengthVarInt64(self.set_status_[i]) + return n + 0 + + def Clear(self): + self.clear_set_status() + + def OutputUnchecked(self, out): + for i in xrange(len(self.set_status_)): + out.putVarInt32(8) + out.putVarInt32(self.set_status_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.add_set_status(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.set_status_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("set_status%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kset_status = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "set_status", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheDeleteRequest_Item(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_delete_time_ = 0 + delete_time_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def delete_time(self): return self.delete_time_ + + def set_delete_time(self, x): + self.has_delete_time_ = 1 + self.delete_time_ = x + + def clear_delete_time(self): + if self.has_delete_time_: + self.has_delete_time_ = 0 + self.delete_time_ = 0 + + def has_delete_time(self): return self.has_delete_time_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_delete_time()): self.set_delete_time(x.delete_time()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_delete_time_ != x.has_delete_time_: return 0 + if self.has_delete_time_ and self.delete_time_ != x.delete_time_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + if (self.has_delete_time_): n += 5 + return n + 1 + + def Clear(self): + self.clear_key() + self.clear_delete_time() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.key_) + if (self.has_delete_time_): + out.putVarInt32(29) + out.put32(self.delete_time_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_key(d.getPrefixedString()) + continue + if tt == 29: + self.set_delete_time(d.get32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_delete_time_: res+=prefix+("delete_time: %s\n" % self.DebugFormatFixed32(self.delete_time_)) + return res + +class MemcacheDeleteRequest(ProtocolBuffer.ProtocolMessage): + has_name_space_ = 0 + name_space_ = "" + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheDeleteRequest_Item() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + if (x.has_name_space()): self.set_name_space(x.name_space()) + + def Equals(self, x): + if x is self: return 1 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.item_) + for i in xrange(len(self.item_)): n += self.item_[i].ByteSize() + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + return n + 0 + + def Clear(self): + self.clear_item() + self.clear_name_space() + + def OutputUnchecked(self, out): + for i in xrange(len(self.item_)): + out.putVarInt32(11) + self.item_[i].OutputUnchecked(out) + out.putVarInt32(12) + if (self.has_name_space_): + out.putVarInt32(34) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_item().TryMerge(d) + continue + if tt == 34: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Item%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kItemGroup = 1 + kItemkey = 2 + kItemdelete_time = 3 + kname_space = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Item", + 2: "key", + 3: "delete_time", + 4: "name_space", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.FLOAT, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheDeleteResponse(ProtocolBuffer.ProtocolMessage): + + DELETED = 1 + NOT_FOUND = 2 + + _DeleteStatusCode_NAMES = { + 1: "DELETED", + 2: "NOT_FOUND", + } + + def DeleteStatusCode_Name(cls, x): return cls._DeleteStatusCode_NAMES.get(x, "") + DeleteStatusCode_Name = classmethod(DeleteStatusCode_Name) + + + def __init__(self, contents=None): + self.delete_status_ = [] + if contents is not None: self.MergeFromString(contents) + + def delete_status_size(self): return len(self.delete_status_) + def delete_status_list(self): return self.delete_status_ + + def delete_status(self, i): + return self.delete_status_[i] + + def set_delete_status(self, i, x): + self.delete_status_[i] = x + + def add_delete_status(self, x): + self.delete_status_.append(x) + + def clear_delete_status(self): + self.delete_status_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.delete_status_size()): self.add_delete_status(x.delete_status(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.delete_status_) != len(x.delete_status_): return 0 + for e1, e2 in zip(self.delete_status_, x.delete_status_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.delete_status_) + for i in xrange(len(self.delete_status_)): n += self.lengthVarInt64(self.delete_status_[i]) + return n + 0 + + def Clear(self): + self.clear_delete_status() + + def OutputUnchecked(self, out): + for i in xrange(len(self.delete_status_)): + out.putVarInt32(8) + out.putVarInt32(self.delete_status_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.add_delete_status(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.delete_status_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("delete_status%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdelete_status = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "delete_status", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheIncrementRequest(ProtocolBuffer.ProtocolMessage): + + INCREMENT = 1 + DECREMENT = 2 + + _Direction_NAMES = { + 1: "INCREMENT", + 2: "DECREMENT", + } + + def Direction_Name(cls, x): return cls._Direction_NAMES.get(x, "") + Direction_Name = classmethod(Direction_Name) + + has_key_ = 0 + key_ = "" + has_name_space_ = 0 + name_space_ = "" + has_delta_ = 0 + delta_ = 1 + has_direction_ = 0 + direction_ = 1 + has_initial_value_ = 0 + initial_value_ = 0 + has_initial_flags_ = 0 + initial_flags_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def delta(self): return self.delta_ + + def set_delta(self, x): + self.has_delta_ = 1 + self.delta_ = x + + def clear_delta(self): + if self.has_delta_: + self.has_delta_ = 0 + self.delta_ = 1 + + def has_delta(self): return self.has_delta_ + + def direction(self): return self.direction_ + + def set_direction(self, x): + self.has_direction_ = 1 + self.direction_ = x + + def clear_direction(self): + if self.has_direction_: + self.has_direction_ = 0 + self.direction_ = 1 + + def has_direction(self): return self.has_direction_ + + def initial_value(self): return self.initial_value_ + + def set_initial_value(self, x): + self.has_initial_value_ = 1 + self.initial_value_ = x + + def clear_initial_value(self): + if self.has_initial_value_: + self.has_initial_value_ = 0 + self.initial_value_ = 0 + + def has_initial_value(self): return self.has_initial_value_ + + def initial_flags(self): return self.initial_flags_ + + def set_initial_flags(self, x): + self.has_initial_flags_ = 1 + self.initial_flags_ = x + + def clear_initial_flags(self): + if self.has_initial_flags_: + self.has_initial_flags_ = 0 + self.initial_flags_ = 0 + + def has_initial_flags(self): return self.has_initial_flags_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_delta()): self.set_delta(x.delta()) + if (x.has_direction()): self.set_direction(x.direction()) + if (x.has_initial_value()): self.set_initial_value(x.initial_value()) + if (x.has_initial_flags()): self.set_initial_flags(x.initial_flags()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_delta_ != x.has_delta_: return 0 + if self.has_delta_ and self.delta_ != x.delta_: return 0 + if self.has_direction_ != x.has_direction_: return 0 + if self.has_direction_ and self.direction_ != x.direction_: return 0 + if self.has_initial_value_ != x.has_initial_value_: return 0 + if self.has_initial_value_ and self.initial_value_ != x.initial_value_: return 0 + if self.has_initial_flags_ != x.has_initial_flags_: return 0 + if self.has_initial_flags_ and self.initial_flags_ != x.initial_flags_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + if (self.has_delta_): n += 1 + self.lengthVarInt64(self.delta_) + if (self.has_direction_): n += 1 + self.lengthVarInt64(self.direction_) + if (self.has_initial_value_): n += 1 + self.lengthVarInt64(self.initial_value_) + if (self.has_initial_flags_): n += 5 + return n + 1 + + def Clear(self): + self.clear_key() + self.clear_name_space() + self.clear_delta() + self.clear_direction() + self.clear_initial_value() + self.clear_initial_flags() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.key_) + if (self.has_delta_): + out.putVarInt32(16) + out.putVarUint64(self.delta_) + if (self.has_direction_): + out.putVarInt32(24) + out.putVarInt32(self.direction_) + if (self.has_name_space_): + out.putVarInt32(34) + out.putPrefixedString(self.name_space_) + if (self.has_initial_value_): + out.putVarInt32(40) + out.putVarUint64(self.initial_value_) + if (self.has_initial_flags_): + out.putVarInt32(53) + out.put32(self.initial_flags_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_key(d.getPrefixedString()) + continue + if tt == 16: + self.set_delta(d.getVarUint64()) + continue + if tt == 24: + self.set_direction(d.getVarInt32()) + continue + if tt == 34: + self.set_name_space(d.getPrefixedString()) + continue + if tt == 40: + self.set_initial_value(d.getVarUint64()) + continue + if tt == 53: + self.set_initial_flags(d.get32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_delta_: res+=prefix+("delta: %s\n" % self.DebugFormatInt64(self.delta_)) + if self.has_direction_: res+=prefix+("direction: %s\n" % self.DebugFormatInt32(self.direction_)) + if self.has_initial_value_: res+=prefix+("initial_value: %s\n" % self.DebugFormatInt64(self.initial_value_)) + if self.has_initial_flags_: res+=prefix+("initial_flags: %s\n" % self.DebugFormatFixed32(self.initial_flags_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + kname_space = 4 + kdelta = 2 + kdirection = 3 + kinitial_value = 5 + kinitial_flags = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "delta", + 3: "direction", + 4: "name_space", + 5: "initial_value", + 6: "initial_flags", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.FLOAT, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheIncrementResponse(ProtocolBuffer.ProtocolMessage): + + OK = 1 + NOT_CHANGED = 2 + ERROR = 3 + + _IncrementStatusCode_NAMES = { + 1: "OK", + 2: "NOT_CHANGED", + 3: "ERROR", + } + + def IncrementStatusCode_Name(cls, x): return cls._IncrementStatusCode_NAMES.get(x, "") + IncrementStatusCode_Name = classmethod(IncrementStatusCode_Name) + + has_new_value_ = 0 + new_value_ = 0 + has_increment_status_ = 0 + increment_status_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def new_value(self): return self.new_value_ + + def set_new_value(self, x): + self.has_new_value_ = 1 + self.new_value_ = x + + def clear_new_value(self): + if self.has_new_value_: + self.has_new_value_ = 0 + self.new_value_ = 0 + + def has_new_value(self): return self.has_new_value_ + + def increment_status(self): return self.increment_status_ + + def set_increment_status(self, x): + self.has_increment_status_ = 1 + self.increment_status_ = x + + def clear_increment_status(self): + if self.has_increment_status_: + self.has_increment_status_ = 0 + self.increment_status_ = 0 + + def has_increment_status(self): return self.has_increment_status_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_new_value()): self.set_new_value(x.new_value()) + if (x.has_increment_status()): self.set_increment_status(x.increment_status()) + + def Equals(self, x): + if x is self: return 1 + if self.has_new_value_ != x.has_new_value_: return 0 + if self.has_new_value_ and self.new_value_ != x.new_value_: return 0 + if self.has_increment_status_ != x.has_increment_status_: return 0 + if self.has_increment_status_ and self.increment_status_ != x.increment_status_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_new_value_): n += 1 + self.lengthVarInt64(self.new_value_) + if (self.has_increment_status_): n += 1 + self.lengthVarInt64(self.increment_status_) + return n + 0 + + def Clear(self): + self.clear_new_value() + self.clear_increment_status() + + def OutputUnchecked(self, out): + if (self.has_new_value_): + out.putVarInt32(8) + out.putVarUint64(self.new_value_) + if (self.has_increment_status_): + out.putVarInt32(16) + out.putVarInt32(self.increment_status_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_new_value(d.getVarUint64()) + continue + if tt == 16: + self.set_increment_status(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_new_value_: res+=prefix+("new_value: %s\n" % self.DebugFormatInt64(self.new_value_)) + if self.has_increment_status_: res+=prefix+("increment_status: %s\n" % self.DebugFormatInt32(self.increment_status_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + knew_value = 1 + kincrement_status = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "new_value", + 2: "increment_status", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheBatchIncrementRequest(ProtocolBuffer.ProtocolMessage): + has_name_space_ = 0 + name_space_ = "" + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheIncrementRequest() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_name_space()): self.set_name_space(x.name_space()) + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + n += 1 * len(self.item_) + for i in xrange(len(self.item_)): n += self.lengthString(self.item_[i].ByteSize()) + return n + 0 + + def Clear(self): + self.clear_name_space() + self.clear_item() + + def OutputUnchecked(self, out): + if (self.has_name_space_): + out.putVarInt32(10) + out.putPrefixedString(self.name_space_) + for i in xrange(len(self.item_)): + out.putVarInt32(18) + out.putVarInt32(self.item_[i].ByteSize()) + self.item_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_name_space(d.getPrefixedString()) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_item().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("item%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kname_space = 1 + kitem = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "name_space", + 2: "item", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheBatchIncrementResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheIncrementResponse() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.item_) + for i in xrange(len(self.item_)): n += self.lengthString(self.item_[i].ByteSize()) + return n + 0 + + def Clear(self): + self.clear_item() + + def OutputUnchecked(self, out): + for i in xrange(len(self.item_)): + out.putVarInt32(10) + out.putVarInt32(self.item_[i].ByteSize()) + self.item_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_item().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("item%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kitem = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "item", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheFlushRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheFlushResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheStatsRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MergedNamespaceStats(ProtocolBuffer.ProtocolMessage): + has_hits_ = 0 + hits_ = 0 + has_misses_ = 0 + misses_ = 0 + has_byte_hits_ = 0 + byte_hits_ = 0 + has_items_ = 0 + items_ = 0 + has_bytes_ = 0 + bytes_ = 0 + has_oldest_item_age_ = 0 + oldest_item_age_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def hits(self): return self.hits_ + + def set_hits(self, x): + self.has_hits_ = 1 + self.hits_ = x + + def clear_hits(self): + if self.has_hits_: + self.has_hits_ = 0 + self.hits_ = 0 + + def has_hits(self): return self.has_hits_ + + def misses(self): return self.misses_ + + def set_misses(self, x): + self.has_misses_ = 1 + self.misses_ = x + + def clear_misses(self): + if self.has_misses_: + self.has_misses_ = 0 + self.misses_ = 0 + + def has_misses(self): return self.has_misses_ + + def byte_hits(self): return self.byte_hits_ + + def set_byte_hits(self, x): + self.has_byte_hits_ = 1 + self.byte_hits_ = x + + def clear_byte_hits(self): + if self.has_byte_hits_: + self.has_byte_hits_ = 0 + self.byte_hits_ = 0 + + def has_byte_hits(self): return self.has_byte_hits_ + + def items(self): return self.items_ + + def set_items(self, x): + self.has_items_ = 1 + self.items_ = x + + def clear_items(self): + if self.has_items_: + self.has_items_ = 0 + self.items_ = 0 + + def has_items(self): return self.has_items_ + + def bytes(self): return self.bytes_ + + def set_bytes(self, x): + self.has_bytes_ = 1 + self.bytes_ = x + + def clear_bytes(self): + if self.has_bytes_: + self.has_bytes_ = 0 + self.bytes_ = 0 + + def has_bytes(self): return self.has_bytes_ + + def oldest_item_age(self): return self.oldest_item_age_ + + def set_oldest_item_age(self, x): + self.has_oldest_item_age_ = 1 + self.oldest_item_age_ = x + + def clear_oldest_item_age(self): + if self.has_oldest_item_age_: + self.has_oldest_item_age_ = 0 + self.oldest_item_age_ = 0 + + def has_oldest_item_age(self): return self.has_oldest_item_age_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_hits()): self.set_hits(x.hits()) + if (x.has_misses()): self.set_misses(x.misses()) + if (x.has_byte_hits()): self.set_byte_hits(x.byte_hits()) + if (x.has_items()): self.set_items(x.items()) + if (x.has_bytes()): self.set_bytes(x.bytes()) + if (x.has_oldest_item_age()): self.set_oldest_item_age(x.oldest_item_age()) + + def Equals(self, x): + if x is self: return 1 + if self.has_hits_ != x.has_hits_: return 0 + if self.has_hits_ and self.hits_ != x.hits_: return 0 + if self.has_misses_ != x.has_misses_: return 0 + if self.has_misses_ and self.misses_ != x.misses_: return 0 + if self.has_byte_hits_ != x.has_byte_hits_: return 0 + if self.has_byte_hits_ and self.byte_hits_ != x.byte_hits_: return 0 + if self.has_items_ != x.has_items_: return 0 + if self.has_items_ and self.items_ != x.items_: return 0 + if self.has_bytes_ != x.has_bytes_: return 0 + if self.has_bytes_ and self.bytes_ != x.bytes_: return 0 + if self.has_oldest_item_age_ != x.has_oldest_item_age_: return 0 + if self.has_oldest_item_age_ and self.oldest_item_age_ != x.oldest_item_age_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_hits_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: hits not set.') + if (not self.has_misses_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: misses not set.') + if (not self.has_byte_hits_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: byte_hits not set.') + if (not self.has_items_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: items not set.') + if (not self.has_bytes_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: bytes not set.') + if (not self.has_oldest_item_age_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: oldest_item_age not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.hits_) + n += self.lengthVarInt64(self.misses_) + n += self.lengthVarInt64(self.byte_hits_) + n += self.lengthVarInt64(self.items_) + n += self.lengthVarInt64(self.bytes_) + return n + 10 + + def Clear(self): + self.clear_hits() + self.clear_misses() + self.clear_byte_hits() + self.clear_items() + self.clear_bytes() + self.clear_oldest_item_age() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarUint64(self.hits_) + out.putVarInt32(16) + out.putVarUint64(self.misses_) + out.putVarInt32(24) + out.putVarUint64(self.byte_hits_) + out.putVarInt32(32) + out.putVarUint64(self.items_) + out.putVarInt32(40) + out.putVarUint64(self.bytes_) + out.putVarInt32(53) + out.put32(self.oldest_item_age_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_hits(d.getVarUint64()) + continue + if tt == 16: + self.set_misses(d.getVarUint64()) + continue + if tt == 24: + self.set_byte_hits(d.getVarUint64()) + continue + if tt == 32: + self.set_items(d.getVarUint64()) + continue + if tt == 40: + self.set_bytes(d.getVarUint64()) + continue + if tt == 53: + self.set_oldest_item_age(d.get32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_hits_: res+=prefix+("hits: %s\n" % self.DebugFormatInt64(self.hits_)) + if self.has_misses_: res+=prefix+("misses: %s\n" % self.DebugFormatInt64(self.misses_)) + if self.has_byte_hits_: res+=prefix+("byte_hits: %s\n" % self.DebugFormatInt64(self.byte_hits_)) + if self.has_items_: res+=prefix+("items: %s\n" % self.DebugFormatInt64(self.items_)) + if self.has_bytes_: res+=prefix+("bytes: %s\n" % self.DebugFormatInt64(self.bytes_)) + if self.has_oldest_item_age_: res+=prefix+("oldest_item_age: %s\n" % self.DebugFormatFixed32(self.oldest_item_age_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + khits = 1 + kmisses = 2 + kbyte_hits = 3 + kitems = 4 + kbytes = 5 + koldest_item_age = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "hits", + 2: "misses", + 3: "byte_hits", + 4: "items", + 5: "bytes", + 6: "oldest_item_age", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.FLOAT, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheStatsResponse(ProtocolBuffer.ProtocolMessage): + has_stats_ = 0 + stats_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def stats(self): + if self.stats_ is None: + self.lazy_init_lock_.acquire() + try: + if self.stats_ is None: self.stats_ = MergedNamespaceStats() + finally: + self.lazy_init_lock_.release() + return self.stats_ + + def mutable_stats(self): self.has_stats_ = 1; return self.stats() + + def clear_stats(self): + if self.has_stats_: + self.has_stats_ = 0; + if self.stats_ is not None: self.stats_.Clear() + + def has_stats(self): return self.has_stats_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_stats()): self.mutable_stats().MergeFrom(x.stats()) + + def Equals(self, x): + if x is self: return 1 + if self.has_stats_ != x.has_stats_: return 0 + if self.has_stats_ and self.stats_ != x.stats_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_stats_ and not self.stats_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_stats_): n += 1 + self.lengthString(self.stats_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_stats() + + def OutputUnchecked(self, out): + if (self.has_stats_): + out.putVarInt32(10) + out.putVarInt32(self.stats_.ByteSize()) + self.stats_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_stats().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_stats_: + res+=prefix+"stats <\n" + res+=self.stats_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kstats = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "stats", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheGrabTailRequest(ProtocolBuffer.ProtocolMessage): + has_item_count_ = 0 + item_count_ = 0 + has_name_space_ = 0 + name_space_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def item_count(self): return self.item_count_ + + def set_item_count(self, x): + self.has_item_count_ = 1 + self.item_count_ = x + + def clear_item_count(self): + if self.has_item_count_: + self.has_item_count_ = 0 + self.item_count_ = 0 + + def has_item_count(self): return self.has_item_count_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_item_count()): self.set_item_count(x.item_count()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + + def Equals(self, x): + if x is self: return 1 + if self.has_item_count_ != x.has_item_count_: return 0 + if self.has_item_count_ and self.item_count_ != x.item_count_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_item_count_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: item_count not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.item_count_) + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + return n + 1 + + def Clear(self): + self.clear_item_count() + self.clear_name_space() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.item_count_) + if (self.has_name_space_): + out.putVarInt32(18) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_item_count(d.getVarInt32()) + continue + if tt == 18: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_item_count_: res+=prefix+("item_count: %s\n" % self.DebugFormatInt32(self.item_count_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kitem_count = 1 + kname_space = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "item_count", + 2: "name_space", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class MemcacheGrabTailResponse_Item(ProtocolBuffer.ProtocolMessage): + has_value_ = 0 + value_ = "" + has_flags_ = 0 + flags_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + def flags(self): return self.flags_ + + def set_flags(self, x): + self.has_flags_ = 1 + self.flags_ = x + + def clear_flags(self): + if self.has_flags_: + self.has_flags_ = 0 + self.flags_ = 0 + + def has_flags(self): return self.has_flags_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_value()): self.set_value(x.value()) + if (x.has_flags()): self.set_flags(x.flags()) + + def Equals(self, x): + if x is self: return 1 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + if self.has_flags_ != x.has_flags_: return 0 + if self.has_flags_ and self.flags_ != x.flags_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.value_)) + if (self.has_flags_): n += 5 + return n + 1 + + def Clear(self): + self.clear_value() + self.clear_flags() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.value_) + if (self.has_flags_): + out.putVarInt32(29) + out.put32(self.flags_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_value(d.getPrefixedString()) + continue + if tt == 29: + self.set_flags(d.get32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + if self.has_flags_: res+=prefix+("flags: %s\n" % self.DebugFormatFixed32(self.flags_)) + return res + +class MemcacheGrabTailResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.item_ = [] + if contents is not None: self.MergeFromString(contents) + + def item_size(self): return len(self.item_) + def item_list(self): return self.item_ + + def item(self, i): + return self.item_[i] + + def mutable_item(self, i): + return self.item_[i] + + def add_item(self): + x = MemcacheGrabTailResponse_Item() + self.item_.append(x) + return x + + def clear_item(self): + self.item_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.item_size()): self.add_item().CopyFrom(x.item(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.item_) != len(x.item_): return 0 + for e1, e2 in zip(self.item_, x.item_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.item_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.item_) + for i in xrange(len(self.item_)): n += self.item_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_item() + + def OutputUnchecked(self, out): + for i in xrange(len(self.item_)): + out.putVarInt32(11) + self.item_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_item().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.item_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Item%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kItemGroup = 1 + kItemvalue = 2 + kItemflags = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Item", + 2: "value", + 3: "flags", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.FLOAT, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['MemcacheServiceError','MemcacheGetRequest','MemcacheGetResponse','MemcacheGetResponse_Item','MemcacheSetRequest','MemcacheSetRequest_Item','MemcacheSetResponse','MemcacheDeleteRequest','MemcacheDeleteRequest_Item','MemcacheDeleteResponse','MemcacheIncrementRequest','MemcacheIncrementResponse','MemcacheBatchIncrementRequest','MemcacheBatchIncrementResponse','MemcacheFlushRequest','MemcacheFlushResponse','MemcacheStatsRequest','MergedNamespaceStats','MemcacheStatsResponse','MemcacheGrabTailRequest','MemcacheGrabTailResponse','MemcacheGrabTailResponse_Item'] diff --git a/google_appengine/google/appengine/api/memcache/memcache_service_pb.pyc b/google_appengine/google/appengine/api/memcache/memcache_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68c4553130145cc34d59eb370fd72fda17c107ae GIT binary patch literal 133790 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 (86400 * 30): + self.expiration_time = expiration + else: + self.expiration_time = self._gettime() + expiration + + def CheckExpired(self): + """Returns True if this entry has expired; False otherwise.""" + return self.will_expire and self._gettime() >= self.expiration_time + + def ExpireAndLock(self, timeout): + """Marks this entry as deleted and locks it for the expiration time. + + Used to implement memcache's delete timeout behavior. + + Args: + timeout: Parameter originally passed to memcache.delete or + memcache.delete_multi to control deletion timeout. + """ + self.will_expire = True + self.locked = True + self._SetExpiration(timeout) + + def CheckLocked(self): + """Returns True if this entry was deleted but has not yet timed out.""" + return self.locked and not self.CheckExpired() + + +class MemcacheServiceStub(apiproxy_stub.APIProxyStub): + """Python only memcache service stub. + + This stub keeps all data in the local process' memory, not in any + external servers. + """ + + def __init__(self, gettime=time.time, service_name='memcache'): + """Initializer. + + Args: + gettime: time.time()-like function used for testing. + service_name: Service name expected for all calls. + """ + super(MemcacheServiceStub, self).__init__(service_name) + self._gettime = lambda: int(gettime()) + self._ResetStats() + + self._the_cache = {} + + def _ResetStats(self): + """Resets statistics information.""" + self._hits = 0 + self._misses = 0 + self._byte_hits = 0 + self._cache_creation_time = self._gettime() + + def _GetKey(self, namespace, key): + """Retrieves a CacheEntry from the cache if it hasn't expired. + + Does not take deletion timeout into account. + + Args: + namespace: The namespace that keys are stored under. + key: The key to retrieve from the cache. + + Returns: + The corresponding CacheEntry instance, or None if it was not found or + has already expired. + """ + namespace_dict = self._the_cache.get(namespace, None) + if namespace_dict is None: + return None + entry = namespace_dict.get(key, None) + if entry is None: + return None + elif entry.CheckExpired(): + del namespace_dict[key] + return None + else: + return entry + + def _Dynamic_Get(self, request, response): + """Implementation of MemcacheService::Get(). + + Args: + request: A MemcacheGetRequest. + response: A MemcacheGetResponse. + """ + namespace = request.name_space() + keys = set(request.key_list()) + for key in keys: + entry = self._GetKey(namespace, key) + if entry is None or entry.CheckLocked(): + self._misses += 1 + continue + self._hits += 1 + self._byte_hits += len(entry.value) + item = response.add_item() + item.set_key(key) + item.set_value(entry.value) + item.set_flags(entry.flags) + + def _Dynamic_Set(self, request, response): + """Implementation of MemcacheService::Set(). + + Args: + request: A MemcacheSetRequest. + response: A MemcacheSetResponse. + """ + namespace = request.name_space() + for item in request.item_list(): + key = item.key() + set_policy = item.set_policy() + old_entry = self._GetKey(namespace, key) + + set_status = MemcacheSetResponse.NOT_STORED + if ((set_policy == MemcacheSetRequest.SET) or + (set_policy == MemcacheSetRequest.ADD and old_entry is None) or + (set_policy == MemcacheSetRequest.REPLACE and old_entry is not None)): + + if (old_entry is None or + set_policy == MemcacheSetRequest.SET + or not old_entry.CheckLocked()): + if namespace not in self._the_cache: + self._the_cache[namespace] = {} + self._the_cache[namespace][key] = CacheEntry(item.value(), + item.expiration_time(), + item.flags(), + gettime=self._gettime) + set_status = MemcacheSetResponse.STORED + + response.add_set_status(set_status) + + def _Dynamic_Delete(self, request, response): + """Implementation of MemcacheService::Delete(). + + Args: + request: A MemcacheDeleteRequest. + response: A MemcacheDeleteResponse. + """ + namespace = request.name_space() + for item in request.item_list(): + key = item.key() + entry = self._GetKey(namespace, key) + + delete_status = MemcacheDeleteResponse.DELETED + if entry is None: + delete_status = MemcacheDeleteResponse.NOT_FOUND + elif item.delete_time() == 0: + del self._the_cache[namespace][key] + else: + entry.ExpireAndLock(item.delete_time()) + + response.add_delete_status(delete_status) + + def _internal_increment(self, namespace, request): + """Internal function for incrementing from a MemcacheIncrementRequest. + + Args: + namespace: A string containing the namespace for the request, if any. + Pass an empty string if there is no namespace. + request: A MemcacheIncrementRequest instance. + + Returns: + An integer or long if the offset was successful, None on error. + """ + key = request.key() + entry = self._GetKey(namespace, key) + if entry is None: + if not request.has_initial_value(): + return None + if namespace not in self._the_cache: + self._the_cache[namespace] = {} + self._the_cache[namespace][key] = CacheEntry(str(request.initial_value()), + expiration=0, + flags=0, + gettime=self._gettime) + entry = self._GetKey(namespace, key) + assert entry is not None + + try: + old_value = long(entry.value) + if old_value < 0: + raise ValueError + except ValueError: + logging.error('Increment/decrement failed: Could not interpret ' + 'value for key = "%s" as an unsigned integer.', key) + return None + + delta = request.delta() + if request.direction() == MemcacheIncrementRequest.DECREMENT: + delta = -delta + + new_value = old_value + delta + if not (0 <= new_value < 2**64): + new_value = 0 + + entry.value = str(new_value) + return new_value + + def _Dynamic_Increment(self, request, response): + """Implementation of MemcacheService::Increment(). + + Args: + request: A MemcacheIncrementRequest. + response: A MemcacheIncrementResponse. + """ + namespace = request.name_space() + new_value = self._internal_increment(namespace, request) + if new_value is None: + raise apiproxy_errors.ApplicationError( + memcache_service_pb.MemcacheServiceError.UNSPECIFIED_ERROR) + response.set_new_value(new_value) + + def _Dynamic_BatchIncrement(self, request, response): + """Implementation of MemcacheService::BatchIncrement(). + + Args: + request: A MemcacheBatchIncrementRequest. + response: A MemcacheBatchIncrementResponse. + """ + namespace = request.name_space() + for request_item in request.item_list(): + new_value = self._internal_increment(namespace, request_item) + item = response.add_item() + if new_value is None: + item.set_increment_status(MemcacheIncrementResponse.NOT_CHANGED) + else: + item.set_increment_status(MemcacheIncrementResponse.OK) + item.set_new_value(new_value) + + def _Dynamic_FlushAll(self, request, response): + """Implementation of MemcacheService::FlushAll(). + + Args: + request: A MemcacheFlushRequest. + response: A MemcacheFlushResponse. + """ + self._the_cache.clear() + self._ResetStats() + + def _Dynamic_Stats(self, request, response): + """Implementation of MemcacheService::Stats(). + + Args: + request: A MemcacheStatsRequest. + response: A MemcacheStatsResponse. + """ + stats = response.mutable_stats() + stats.set_hits(self._hits) + stats.set_misses(self._misses) + stats.set_byte_hits(self._byte_hits) + items = 0 + total_bytes = 0 + for namespace in self._the_cache.itervalues(): + items += len(namespace) + for entry in namespace.itervalues(): + total_bytes += len(entry.value) + stats.set_items(items) + stats.set_bytes(total_bytes) + + stats.set_oldest_item_age(self._gettime() - self._cache_creation_time) + diff --git a/google_appengine/google/appengine/api/memcache/memcache_stub.pyc b/google_appengine/google/appengine/api/memcache/memcache_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7ac00431f67507d57381a40cb8f37cbba7b4340 GIT binary patch literal 11923 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/namespace_manager/__init__.py b/google_appengine/google/appengine/api/namespace_manager/__init__.py new file mode 100755 index 0000000..fe3e795 --- /dev/null +++ b/google_appengine/google/appengine/api/namespace_manager/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Namespace Manager Module.""" + + +from namespace_manager import * diff --git a/google_appengine/google/appengine/api/namespace_manager/__init__.pyc b/google_appengine/google/appengine/api/namespace_manager/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4631ed89ae61f0d60c8e91c1af000a7dcc8bd57 GIT binary patch literal 274 zcwS|Wu?oU45QfuMR0IdbMR4d?9GVvpad5OQ6Ie^q&T_3M6s<-3i*uV28yU`1*x=Bd@ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/namespace_manager/namespace_manager.py b/google_appengine/google/appengine/api/namespace_manager/namespace_manager.py new file mode 100755 index 0000000..8204d0f --- /dev/null +++ b/google_appengine/google/appengine/api/namespace_manager/namespace_manager.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Control the namespacing system used by various APIs. + +A namespace may be specified in various API calls exemplified +by the datastore and memcache interfaces. The default can be +specified using this module. +""" + + + +import os +import re +import warnings + +from google.appengine.api import lib_config + +__all__ = ['BadValueError', + 'set_namespace', + 'get_namespace', + 'enable_request_namespace', + 'validate_namespace', + ] + + +_ENV_DEFAULT_NAMESPACE = 'HTTP_X_APPENGINE_DEFAULT_NAMESPACE' +_ENV_CURRENT_NAMESPACE = 'HTTP_X_APPENGINE_CURRENT_NAMESPACE' + +_NAMESPACE_MAX_LENGTH = 100 +_NAMESPACE_PATTERN = r'^[0-9A-Za-z._-]{0,%s}$' % _NAMESPACE_MAX_LENGTH +_NAMESPACE_RE = re.compile(_NAMESPACE_PATTERN) + +class _ConfigDefaults(object): + def default_namespace_for_request(): + return None + +_config = lib_config.register('namespace_manager_', _ConfigDefaults.__dict__) + +def set_namespace(namespace): + """Set the default namespace for the current HTTP request. + + Args: + namespace: A string naming the new namespace to use. A value of None + will unset the default namespace value. + """ + if namespace is None: + os.environ.pop(_ENV_CURRENT_NAMESPACE, None) + else: + validate_namespace(namespace) + os.environ[_ENV_CURRENT_NAMESPACE] = namespace + + +def get_namespace(): + """Get the the current default namespace or ('') namespace if unset.""" + name = os.environ.get(_ENV_CURRENT_NAMESPACE, None) + if name is None: + name = _config.default_namespace_for_request() + if name is None: + name = '' + return name + + +def enable_request_namespace(): + """Set the default namespace to the Google Apps domain referring this request. + + This method is deprecated, use lib_config instead. + + Calling this function will set the default namespace to the + Google Apps domain that was used to create the url used for this request + and only for the current request and only if the current default namespace + is unset. + + """ + warnings.warn('namespace_manager.enable_request_namespace() is deprecated: ' + 'use lib_config instead.', + DeprecationWarning, + stacklevel=2) + if _ENV_CURRENT_NAMESPACE not in os.environ: + if _ENV_DEFAULT_NAMESPACE in os.environ: + os.environ[_ENV_CURRENT_NAMESPACE] = os.environ[_ENV_DEFAULT_NAMESPACE] + + +class BadValueError(Exception): + """Raised by ValidateNamespaceString.""" + + +def validate_namespace(value, exception=BadValueError): + """Raises an exception if value is not a valid Namespace string. + + A namespace must match the regular expression ([0-9A-Za-z._-]{0,100}). + + Args: + value: string, the value to validate. + exception: exception type to raise. + """ + if not isinstance(value, basestring): + raise exception('value should be a string; received %r (a %s):' % + (value, type(value))) + if not _NAMESPACE_RE.match(value): + raise exception('value does not match pattern "%s"' % _NAMESPACE_PATTERN) diff --git a/google_appengine/google/appengine/api/namespace_manager/namespace_manager.pyc b/google_appengine/google/appengine/api/namespace_manager/namespace_manager.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f89a9de411869a5926581ac79edbc052741f038 GIT binary patch literal 4233 zcwWUt>`D>{c#jNr7nTRYpH?~dDb zLV$juJQXj2kaz`n0A2uQX3uAbw0vxBd3`rKH~)5iGjqTHz1;lc_4sZC<)?wakMU>z z!X*Y6;Oar_!y^v{-t^jsfj_-ofWZP^FTkTkNNO;sffp}AT!Xj{@e;%rV1!h>ZomL< zn=rTl@kJOkAie~u0ULuRC?E7iP)&G(dM-h{3~CvMD0KyX0eAxN)nElw4fJJ*FN0bE zy$Vmzj4KeY635^w#8>#@C5W%`#TuX$YMrILR7u$YwE^)O{DOL}kpunKI{Et>F1uN3 zi!2fLNQqRAmC0qK(~&Tz#;UQH7!`}dQ*kT{olT7B?0;_B&1R?KRbnhp#i0@=SCJlS zWY_6DyNG0x7@@vb<2+%8Cdwmqv9!|ItWZLxu^6jyBqJp2)T&~Ll1*EP1L9LdIY}%E zN>NdBrf6cwQhTJ07-#V$QSGMw2S2@*ho0FcE{Q%2qbwcjkzK~yPh@;wCKJ^yimYIY zQ8ui6Van(X>7C11C8}#fsO{LSQotXWhB>#4GW5ge~t$kZX-v( zMJmm&xb&5E04#wu!!kycccMvAsMHEdPf>;#VJ}3d7?~aX7qf?UL`N7~5EPN-5Q()! zoy^I#8C8-tavl@>L^c$ZO)N-=6P+YtlA33l#SgWSyVt6t{Q*^MOYUUGE&&1bSQl8x zC|r4#TS80N?cG0*rl3YA&MDoP5B>Ac1Tz0U(nl!)OUvFhzv(d;&m8s0J#|(A9}KIkMvz4t^T^V?5*Oj3_pEp z)9~M~!jDnJl*6cE(|_W7o@MCi8a!Nr>=qtDeR^MqjF8ATNPVTeT7cW%1&C6QM}>Zi zbeJRW|KNzQp^ZE2tLWsp5pgz_*kB7aR7Fv>zj=rra6eS`D8p8a52(CQ5jLp!7GY4# zIwE#j?C~<@r*^SRSK@|~G_pEN9WtMdOJ^TG{S*uBk+kAO8aF~9XH+P(f+bCg#F1US znK^)>XcoxQn1}zHx+n}KyHq6?O&^|8Y}?Q3?yZ|=!?*LS@Ns)FRz{B! zb*vKYSE%4FbRvrsBWpO}>4uB`UMUx&{H0@XfSKW_Dq`m*hZoelcm}9obFtyqy(I+B z>I^p3xItc2I zQQ*y=)6f}20FKj<${C`F0@qV9S_T!uX83b7SPO0S<%W?)G=BLAPn>j0^cjoN3r#Z70}P-0YBJY$qB;x~aA;=-V7>8x2Q<9BXvzjJWV4SGCZF&CS{^F`(6^G{ha zWJvnRLujmeb?;TgYKvZIg4dxeH9SQXDj=#|!^Np=S1R#J2PC0HA+`yepkJfssb1Oj zv$43>c@W;kSDAy)g6q?VqHLV&LS-*cMLD#`IxYQRfdHD5L{RPi`8;fWR+UG_H? O*ZsyyquE&7So{}Y=3jyU literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/oauth/__init__.py b/google_appengine/google/appengine/api/oauth/__init__.py new file mode 100755 index 0000000..f689462 --- /dev/null +++ b/google_appengine/google/appengine/api/oauth/__init__.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""OAuth API module.""" + +from oauth_api import * + + +__all__ = ['Error', + 'OAuthRequestError', + 'NotAllowedError', + 'InvalidOAuthParametersError', + 'InvalidOAuthTokenError', + 'OAuthServiceFailureError', + 'get_current_user', + 'is_current_user_admin', + 'get_oauth_consumer_key', + ] diff --git a/google_appengine/google/appengine/api/oauth/oauth_api.py b/google_appengine/google/appengine/api/oauth/oauth_api.py new file mode 100755 index 0000000..5905c6e --- /dev/null +++ b/google_appengine/google/appengine/api/oauth/oauth_api.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""OAuth API. + +A service that enables App Engine apps to validate OAuth requests. + +Classes defined here: + Error: base exception type + NotAllowedError: OAuthService exception + OAuthRequestError: OAuthService exception + InvalidOAuthParametersError: OAuthService exception + InvalidOAuthTokenError: OAuthService exception + OAuthServiceFailureError: OAuthService exception +""" + + + + + + + +import os + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import user_service_pb +from google.appengine.api import users +from google.appengine.runtime import apiproxy_errors + + +class Error(Exception): + """Base error class for this module.""" + + +class OAuthRequestError(Error): + """Base error type for invalid OAuth requests.""" + + +class NotAllowedError(OAuthRequestError): + """Raised if the requested URL does not permit OAuth authentication.""" + + +class InvalidOAuthParametersError(OAuthRequestError): + """Raised if the request was a malformed OAuth request. + + For example, the request may have omitted a required parameter, contained + an invalid signature, or was made by an unknown consumer. + """ + + +class InvalidOAuthTokenError(OAuthRequestError): + """Raised if the request contained an invalid token. + + For example, the token may have been revoked by the user. + """ + + +class OAuthServiceFailureError(Error): + """Raised if there was a problem communicating with the OAuth service.""" + + +def get_current_user(): + """Returns the User on whose behalf the request was made. + + Returns: + User + + Raises: + OAuthRequestError: The request was not a valid OAuth request. + OAuthServiceFailureError: An unknown error occurred. + """ + _maybe_call_get_oauth_user() + return _get_user_from_environ() + + +def is_current_user_admin(): + """Returns true if the User on whose behalf the request was made is an admin. + + Returns: + boolean + + Raises: + OAuthRequestError: The request was not a valid OAuth request. + OAuthServiceFailureError: An unknown error occurred. + """ + _maybe_call_get_oauth_user() + return os.environ.get('OAUTH_IS_ADMIN', '0') == '1' + + +def get_oauth_consumer_key(): + """Returns the value of the 'oauth_consumer_key' parameter from the request. + + Returns: + string: The value of the 'oauth_consumer_key' parameter from the request, + an identifier for the consumer that signed the request. + + Raises: + OAuthRequestError: The request was not a valid OAuth request. + OAuthServiceFailureError: An unknown error occurred. + """ + req = user_service_pb.CheckOAuthSignatureRequest() + resp = user_service_pb.CheckOAuthSignatureResponse() + try: + apiproxy_stub_map.MakeSyncCall('user', 'CheckOAuthSignature', req, resp) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + user_service_pb.UserServiceError.OAUTH_INVALID_REQUEST): + raise InvalidOAuthParametersError + elif (e.application_error == + user_service_pb.UserServiceError.OAUTH_ERROR): + raise OAuthServiceFailureError + else: + raise OAuthServiceFailureError + return resp.oauth_consumer_key() + + +def _maybe_call_get_oauth_user(): + """Makes an GetOAuthUser RPC and stores the results in os.environ. + + This method will only make the RPC if 'OAUTH_ERROR_CODE' has not already + been set. + """ + if 'OAUTH_ERROR_CODE' not in os.environ: + req = user_service_pb.GetOAuthUserRequest() + resp = user_service_pb.GetOAuthUserResponse() + try: + apiproxy_stub_map.MakeSyncCall('user', 'GetOAuthUser', req, resp) + os.environ['OAUTH_EMAIL'] = resp.email() + os.environ['OAUTH_AUTH_DOMAIN'] = resp.auth_domain() + os.environ['OAUTH_USER_ID'] = resp.user_id() + if resp.is_admin(): + os.environ['OAUTH_IS_ADMIN'] = '1' + else: + os.environ['OAUTH_IS_ADMIN'] = '0' + os.environ['OAUTH_ERROR_CODE'] = '' + except apiproxy_errors.ApplicationError, e: + os.environ['OAUTH_ERROR_CODE'] = str(e.application_error) + _maybe_raise_exception() + + +def _maybe_raise_exception(): + """Raises an error if one has been stored in os.environ. + + This method requires that 'OAUTH_ERROR_CODE' has already been set (an empty + string indicates that there is no actual error). + """ + assert 'OAUTH_ERROR_CODE' in os.environ + error = os.environ['OAUTH_ERROR_CODE'] + if error: + if error == str(user_service_pb.UserServiceError.NOT_ALLOWED): + raise NotAllowedError + elif error == str(user_service_pb.UserServiceError.OAUTH_INVALID_REQUEST): + raise InvalidOAuthParametersError + elif error == str(user_service_pb.UserServiceError.OAUTH_INVALID_TOKEN): + raise InvalidOAuthTokenError + elif error == str(user_service_pb.UserServiceError.OAUTH_ERROR): + raise OAuthServiceFailureError + else: + raise OAuthServiceFailureError + + +def _get_user_from_environ(): + """Returns a User based on values stored in os.environ. + + This method requires that 'OAUTH_EMAIL', 'OAUTH_AUTH_DOMAIN', and + 'OAUTH_USER_ID' have already been set. + + Returns: + User + """ + assert 'OAUTH_EMAIL' in os.environ + assert 'OAUTH_AUTH_DOMAIN' in os.environ + assert 'OAUTH_USER_ID' in os.environ + return users.User(email=os.environ['OAUTH_EMAIL'], + _auth_domain=os.environ['OAUTH_AUTH_DOMAIN'], + _user_id=os.environ['OAUTH_USER_ID']) diff --git a/google_appengine/google/appengine/api/queueinfo.py b/google_appengine/google/appengine/api/queueinfo.py new file mode 100755 index 0000000..dc881ba --- /dev/null +++ b/google_appengine/google/appengine/api/queueinfo.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""QueueInfo tools. + +A library for working with QueueInfo records, describing task queue entries +for an application. Supports loading the records from queue.yaml. + +A queue has two required parameters and one optional one. The required +parameters are 'name' (must be unique for an appid) and 'rate' (the rate +at which jobs in the queue are run). There is an optional parameter +'bucket_size' that will allow tokens to be 'saved up' (for more on the +algorithm, see http://en.wikipedia.org/wiki/Token_Bucket). rate is expressed +as number/unit, with number being an int or a float, and unit being one of +'s' (seconds), 'm' (minutes), 'h' (hours) or 'd' (days). bucket_size is +an integer. + +An example of the use of bucket_size rate: the free email quota is 2000/d, +and the maximum you can send in a single minute is 11. So we can define a +queue for sending email like this: + +queue: +- name: mail_queue + rate: 2000/d + bucket_size: 10 + +If this queue had been idle for a while before some jobs were submitted to it, +the first 10 jobs submitted would be run immediately, then subsequent ones +would be run once every 40s or so. The limit of 2000 per day would still apply. + +An app's queues are also subject to storage quota limits for their stored tasks, +i.e. those tasks that have been added to queues but not yet executed. This quota +is part of their total storage quota (including datastore and blobstore quota). +We allow an app to override the default portion of this quota available for +taskqueue storage (100M) with a top level field "total_storage_limit". + +total_storage_limit: 1.2G + +If no suffix is specified, the number is interpreted as bytes. Supported +suffices are B (bytes), K (kilobytes), M (megabytes), G (gigabytes) and +T (terabytes). If total_storage_limit exceeds the total disk storage +available to an app, it is clamped. +""" + + + +from google.appengine.api import validation +from google.appengine.api import yaml_builder +from google.appengine.api import yaml_listener +from google.appengine.api import yaml_object + +_NAME_REGEX = r'^[A-Za-z0-9-]{0,499}$' +_RATE_REGEX = r'^(0|[0-9]+(\.[0-9]*)?/[smhd])' +_TOTAL_STORAGE_LIMIT_REGEX = r'^([0-9]+(\.[0-9]*)?[BKMGT]?)' + +QUEUE = 'queue' + +NAME = 'name' +RATE = 'rate' +BUCKET_SIZE = 'bucket_size' +TOTAL_STORAGE_LIMIT = 'total_storage_limit' + +BYTE_SUFFIXES = 'BKMGT' + + +class MalformedQueueConfiguration(Exception): + """Configuration file for Task Queue is malformed.""" + + +class QueueEntry(validation.Validated): + """A queue entry describes a single task queue.""" + ATTRIBUTES = { + NAME: _NAME_REGEX, + RATE: _RATE_REGEX, + BUCKET_SIZE: validation.Optional(validation.TYPE_INT), + } + + +class QueueInfoExternal(validation.Validated): + """QueueInfoExternal describes all queue entries for an application.""" + ATTRIBUTES = { + TOTAL_STORAGE_LIMIT: validation.Optional(_TOTAL_STORAGE_LIMIT_REGEX), + QUEUE: validation.Optional(validation.Repeated(QueueEntry)), + } + + +def LoadSingleQueue(queue_info): + """Load a queue.yaml file or string and return a QueueInfoExternal object. + + Args: + queue_info: the contents of a queue.yaml file, as a string. + + Returns: + A QueueInfoExternal object. + """ + builder = yaml_object.ObjectBuilder(QueueInfoExternal) + handler = yaml_builder.BuilderHandler(builder) + listener = yaml_listener.EventListener(handler) + listener.Parse(queue_info) + + queue_info = handler.GetResults() + if len(queue_info) < 1: + raise MalformedQueueConfiguration('Empty queue configuration.') + if len(queue_info) > 1: + raise MalformedQueueConfiguration('Multiple queue: sections ' + 'in configuration.') + return queue_info[0] + + +def ParseRate(rate): + """Parses a rate string in the form number/unit, or the literal 0. + + The unit is one of s (seconds), m (minutes), h (hours) or d (days). + + Args: + rate: the rate string. + + Returns: + a floating point number representing the rate/second. + + Raises: + MalformedQueueConfiguration: if the rate is invalid + """ + if rate == "0": + return 0.0 + elements = rate.split('/') + if len(elements) != 2: + raise MalformedQueueConfiguration('Rate "%s" is invalid.' % rate) + number, unit = elements + try: + number = float(number) + except ValueError: + raise MalformedQueueConfiguration('Rate "%s" is invalid:' + ' "%s" is not a number.' % + (rate, number)) + if unit not in 'smhd': + raise MalformedQueueConfiguration('Rate "%s" is invalid:' + ' "%s" is not one of s, m, h, d.' % + (rate, unit)) + if unit == 's': + return number + if unit == 'm': + return number/60 + if unit == 'h': + return number/(60 * 60) + if unit == 'd': + return number/(24 * 60 * 60) + +def ParseTotalStorageLimit(limit): + """Parses a string representing the storage bytes limit. + + Optional limit suffixes are: + B (bytes), K (kilobytes), M (megabytes), G (gigabytes), T (terabytes) + + Args: + limit: The storage bytes limit string. + + Returns: + An int representing the storage limit in bytes. + + Raises: + MalformedQueueConfiguration: if the limit argument isn't a valid python + double followed by an optional suffix. + """ + limit = limit.strip() + if not limit: + raise MalformedQueueConfiguration('Total Storage Limit must not be empty.') + try: + if limit[-1] in BYTE_SUFFIXES: + number = float(limit[0:-1]) + for c in BYTE_SUFFIXES: + if limit[-1] != c: + number = number * 1024 + else: + return int(number) + else: + return int(limit) + except ValueError: + raise MalformedQueueConfiguration('Total Storage Limit "%s" is invalid.' % + limit) + diff --git a/google_appengine/google/appengine/api/queueinfo.pyc b/google_appengine/google/appengine/api/queueinfo.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a07de301375fc6da4fff5964b9d486fa489158d8 GIT binary patch literal 6562 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 NSLd&unZGi>`d=q_?py!> literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/quota.py b/google_appengine/google/appengine/api/quota.py new file mode 100755 index 0000000..3168eb2 --- /dev/null +++ b/google_appengine/google/appengine/api/quota.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Access to quota usage for this application.""" + + + + +try: + from google3.apphosting.runtime import _apphosting_runtime___python__apiproxy +except ImportError: + _apphosting_runtime___python__apiproxy = None + +def get_request_cpu_usage(): + """Get the amount of CPU used so far for the current request. + + Returns the number of megacycles used so far for the current + request. Does not include CPU used by API calls. + + Does nothing when used in the dev_appserver. + """ + + if _apphosting_runtime___python__apiproxy: + return _apphosting_runtime___python__apiproxy.get_request_cpu_usage() + return 0 + +def get_request_api_cpu_usage(): + """Get the amount of CPU used so far by API calls during the current request. + + Returns the number of megacycles used so far by API calls for the current + request. Does not include CPU used by code in the request itself. + + Does nothing when used in the dev_appserver. + """ + + if _apphosting_runtime___python__apiproxy: + return _apphosting_runtime___python__apiproxy.get_request_api_cpu_usage() + return 0 + +MCYCLES_PER_SECOND = 1200.0 +"""Megacycles to CPU seconds. Convert by using a 1.2 GHz 64-bit x86 CPU.""" + +def megacycles_to_cpu_seconds(mcycles): + """Convert an input value in megacycles to CPU-seconds. + + Returns a double representing the CPU-seconds the input megacycle value + converts to. + """ + return mcycles / MCYCLES_PER_SECOND + +def cpu_seconds_to_megacycles(cpu_secs): + """Convert an input value in CPU-seconds to megacycles. + + Returns an integer representing the megacycles the input CPU-seconds value + converts to. + """ + return int(cpu_secs * MCYCLES_PER_SECOND) diff --git a/google_appengine/google/appengine/api/urlfetch.py b/google_appengine/google/appengine/api/urlfetch.py new file mode 100755 index 0000000..77cbdf4 --- /dev/null +++ b/google_appengine/google/appengine/api/urlfetch.py @@ -0,0 +1,362 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""URL downloading API. + +Methods defined in this module: + Fetch(): fetchs a given URL using an HTTP GET or POST +""" + + + + + +import os +import UserDict +import urllib2 +import urlparse + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import urlfetch_service_pb +from google.appengine.api.urlfetch_errors import * +from google.appengine.runtime import apiproxy_errors + +MAX_REDIRECTS = 5 + +GET = 1 +POST = 2 +HEAD = 3 +PUT = 4 +DELETE = 5 + + +_URL_STRING_MAP = { + 'GET': GET, + 'POST': POST, + 'HEAD': HEAD, + 'PUT': PUT, + 'DELETE': DELETE, +} + + +_VALID_METHODS = frozenset(_URL_STRING_MAP.values()) + + +class _CaselessDict(UserDict.IterableUserDict): + """Case insensitive dictionary. + + This class was lifted from os.py and slightly modified. + """ + + def __init__(self): + UserDict.IterableUserDict.__init__(self) + self.caseless_keys = {} + + def __setitem__(self, key, item): + """Set dictionary item. + + Args: + key: Key of new item. Key is case insensitive, so "d['Key'] = value " + will replace previous values set by "d['key'] = old_value". + item: Item to store. + """ + caseless_key = key.lower() + if caseless_key in self.caseless_keys: + del self.data[self.caseless_keys[caseless_key]] + self.caseless_keys[caseless_key] = key + self.data[key] = item + + def __getitem__(self, key): + """Get dictionary item. + + Args: + key: Key of item to get. Key is case insensitive, so "d['Key']" is the + same as "d['key']". + + Returns: + Item associated with key. + """ + return self.data[self.caseless_keys[key.lower()]] + + def __delitem__(self, key): + """Remove item from dictionary. + + Args: + key: Key of item to remove. Key is case insensitive, so "del d['Key']" is + the same as "del d['key']" + """ + caseless_key = key.lower() + del self.data[self.caseless_keys[caseless_key]] + del self.caseless_keys[caseless_key] + + def has_key(self, key): + """Determine if dictionary has item with specific key. + + Args: + key: Key to check for presence. Key is case insensitive, so + "d.has_key('Key')" evaluates to the same value as "d.has_key('key')". + + Returns: + True if dictionary contains key, else False. + """ + return key.lower() in self.caseless_keys + + def __contains__(self, key): + """Same as 'has_key', but used for 'in' operator.'""" + return self.has_key(key) + + def get(self, key, failobj=None): + """Get dictionary item, defaulting to another value if it does not exist. + + Args: + key: Key of item to get. Key is case insensitive, so "d['Key']" is the + same as "d['key']". + failobj: Value to return if key not in dictionary. + """ + try: + cased_key = self.caseless_keys[key.lower()] + except KeyError: + return failobj + return self.data[cased_key] + + def update(self, dict=None, **kwargs): + """Update dictionary using values from another dictionary and keywords. + + Args: + dict: Dictionary to update from. + kwargs: Keyword arguments to update from. + """ + if dict: + try: + keys = dict.keys() + except AttributeError: + for k, v in dict: + self[k] = v + else: + for k in keys: + self[k] = dict[k] + if kwargs: + self.update(kwargs) + + def copy(self): + """Make a shallow, case sensitive copy of self.""" + return dict(self) + + +def _is_fetching_self(url, method): + """Checks if the fetch is for the same URL from which it originated. + + Args: + url: str, The URL being fetched. + method: value from _VALID_METHODS. + + Returns: + boolean indicating whether or not it seems that the app is trying to fetch + itself. + """ + if (method != GET or + "HTTP_HOST" not in os.environ or + "PATH_INFO" not in os.environ): + return False + + scheme, host_port, path, query, fragment = urlparse.urlsplit(url) + + if host_port == os.environ['HTTP_HOST']: + current_path = urllib2.unquote(os.environ['PATH_INFO']) + desired_path = urllib2.unquote(path) + + if (current_path == desired_path or + (current_path in ('', '/') and desired_path in ('', '/'))): + return True + + return False + + +def create_rpc(deadline=None, callback=None): + """Creates an RPC object for use with the urlfetch API. + + Args: + deadline: Optional deadline in seconds for the operation; the default + is a system-specific deadline (typically 5 seconds). + callback: Optional callable to invoke on completion. + + Returns: + An apiproxy_stub_map.UserRPC object specialized for this service. + """ + return apiproxy_stub_map.UserRPC('urlfetch', deadline, callback) + + +def fetch(url, payload=None, method=GET, headers={}, + allow_truncated=False, follow_redirects=True, + deadline=None): + """Fetches the given HTTP URL, blocking until the result is returned. + + Other optional parameters are: + method: GET, POST, HEAD, PUT, or DELETE + payload: POST or PUT payload (implies method is not GET, HEAD, or DELETE). + this is ignored if the method is not POST or PUT. + headers: dictionary of HTTP headers to send with the request + allow_truncated: if true, truncate large responses and return them without + error. Otherwise, ResponseTooLargeError is raised when a response is + truncated. + follow_redirects: if true (the default), redirects are + transparently followed and the response (if less than 5 + redirects) contains the final destination's payload and the + response status is 200. You lose, however, the redirect chain + information. If false, you see the HTTP response yourself, + including the 'Location' header, and redirects are not + followed. + deadline: deadline in seconds for the operation. + + We use a HTTP/1.1 compliant proxy to fetch the result. + + The returned data structure has the following fields: + content: string containing the response from the server + status_code: HTTP status code returned by the server + headers: dictionary of headers returned by the server + + If the URL is an empty string or obviously invalid, we throw an + urlfetch.InvalidURLError. If the server cannot be contacted, we throw a + urlfetch.DownloadError. Note that HTTP errors are returned as a part + of the returned structure, so HTTP errors like 404 do not result in an + exception. + """ + rpc = create_rpc(deadline=deadline) + make_fetch_call(rpc, url, payload, method, headers, + allow_truncated, follow_redirects) + return rpc.get_result() + + +def make_fetch_call(rpc, url, payload=None, method=GET, headers={}, + allow_truncated=False, follow_redirects=True): + """Executes the RPC call to fetch a given HTTP URL. + + The first argument is a UserRPC instance. See urlfetch.fetch for a + thorough description of remaining arguments. + """ + assert rpc.service == 'urlfetch', repr(rpc.service) + if isinstance(method, basestring): + method = method.upper() + method = _URL_STRING_MAP.get(method, method) + if method not in _VALID_METHODS: + raise InvalidMethodError('Invalid method %s.' % str(method)) + + if _is_fetching_self(url, method): + raise InvalidURLError("App cannot fetch the same URL as the one used for " + "the request.") + + request = urlfetch_service_pb.URLFetchRequest() + response = urlfetch_service_pb.URLFetchResponse() + request.set_url(url) + + if method == GET: + request.set_method(urlfetch_service_pb.URLFetchRequest.GET) + elif method == POST: + request.set_method(urlfetch_service_pb.URLFetchRequest.POST) + elif method == HEAD: + request.set_method(urlfetch_service_pb.URLFetchRequest.HEAD) + elif method == PUT: + request.set_method(urlfetch_service_pb.URLFetchRequest.PUT) + elif method == DELETE: + request.set_method(urlfetch_service_pb.URLFetchRequest.DELETE) + + if payload and (method == POST or method == PUT): + request.set_payload(payload) + + for key, value in headers.iteritems(): + header_proto = request.add_header() + header_proto.set_key(key) + header_proto.set_value(str(value)) + + request.set_followredirects(follow_redirects) + + if rpc.deadline is not None: + request.set_deadline(rpc.deadline) + + rpc.make_call('Fetch', request, response, _get_fetch_result, allow_truncated) + + +def _get_fetch_result(rpc): + """Check success, handle exceptions, and return converted RPC result. + + This method waits for the RPC if it has not yet finished, and calls the + post-call hooks on the first invocation. + + Args: + rpc: A UserRPC object. + + Raises: + InvalidURLError if the url was invalid. + DownloadError if there was a problem fetching the url. + ResponseTooLargeError if the response was either truncated (and + allow_truncated=False was passed to make_fetch_call()), or if it + was too big for us to download. + + Returns: + A _URLFetchResult object. + """ + assert rpc.service == 'urlfetch', repr(rpc.service) + assert rpc.method == 'Fetch', repr(rpc.method) + try: + rpc.check_success() + except apiproxy_errors.ApplicationError, err: + if (err.application_error == + urlfetch_service_pb.URLFetchServiceError.INVALID_URL): + raise InvalidURLError(str(err)) + if (err.application_error == + urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR): + raise DownloadError(str(err)) + if (err.application_error == + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR): + raise DownloadError(str(err)) + if (err.application_error == + urlfetch_service_pb.URLFetchServiceError.RESPONSE_TOO_LARGE): + raise ResponseTooLargeError(None) + if (err.application_error == + urlfetch_service_pb.URLFetchServiceError.DEADLINE_EXCEEDED): + raise DownloadError(str(err)) + raise err + + response = rpc.response + allow_truncated = rpc.user_data + result = _URLFetchResult(response) + if response.contentwastruncated() and not allow_truncated: + raise ResponseTooLargeError(result) + return result + + +Fetch = fetch + + +class _URLFetchResult(object): + """A Pythonic representation of our fetch response protocol buffer. + """ + + def __init__(self, response_proto): + """Constructor. + + Args: + response_proto: the URLFetchResponse proto buffer to wrap. + """ + self.__pb = response_proto + self.content = response_proto.content() + self.status_code = response_proto.statuscode() + self.content_was_truncated = response_proto.contentwastruncated() + self.headers = _CaselessDict() + self.final_url = response_proto.finalurl() or None + for header_proto in response_proto.header_list(): + self.headers[header_proto.key()] = header_proto.value() diff --git a/google_appengine/google/appengine/api/urlfetch.pyc b/google_appengine/google/appengine/api/urlfetch.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dec47cf298837d3051ae3e2329fa1f7372dc6097 GIT binary patch literal 12492 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/urlfetch_service_pb.py b/google_appengine/google/appengine/api/urlfetch_service_pb.py new file mode 100755 index 0000000..34b6d5c --- /dev/null +++ b/google_appengine/google/appengine/api/urlfetch_service_pb.py @@ -0,0 +1,943 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class URLFetchServiceError(ProtocolBuffer.ProtocolMessage): + + OK = 0 + INVALID_URL = 1 + FETCH_ERROR = 2 + UNSPECIFIED_ERROR = 3 + RESPONSE_TOO_LARGE = 4 + DEADLINE_EXCEEDED = 5 + + _ErrorCode_NAMES = { + 0: "OK", + 1: "INVALID_URL", + 2: "FETCH_ERROR", + 3: "UNSPECIFIED_ERROR", + 4: "RESPONSE_TOO_LARGE", + 5: "DEADLINE_EXCEEDED", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class URLFetchRequest_Header(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(34) + out.putPrefixedString(self.key_) + out.putVarInt32(42) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 28: break + if tt == 34: + self.set_key(d.getPrefixedString()) + continue + if tt == 42: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("Key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("Value: %s\n" % self.DebugFormatString(self.value_)) + return res + +class URLFetchRequest(ProtocolBuffer.ProtocolMessage): + + GET = 1 + POST = 2 + HEAD = 3 + PUT = 4 + DELETE = 5 + + _RequestMethod_NAMES = { + 1: "GET", + 2: "POST", + 3: "HEAD", + 4: "PUT", + 5: "DELETE", + } + + def RequestMethod_Name(cls, x): return cls._RequestMethod_NAMES.get(x, "") + RequestMethod_Name = classmethod(RequestMethod_Name) + + has_method_ = 0 + method_ = 0 + has_url_ = 0 + url_ = "" + has_payload_ = 0 + payload_ = "" + has_followredirects_ = 0 + followredirects_ = 1 + has_deadline_ = 0 + deadline_ = 0.0 + + def __init__(self, contents=None): + self.header_ = [] + if contents is not None: self.MergeFromString(contents) + + def method(self): return self.method_ + + def set_method(self, x): + self.has_method_ = 1 + self.method_ = x + + def clear_method(self): + if self.has_method_: + self.has_method_ = 0 + self.method_ = 0 + + def has_method(self): return self.has_method_ + + def url(self): return self.url_ + + def set_url(self, x): + self.has_url_ = 1 + self.url_ = x + + def clear_url(self): + if self.has_url_: + self.has_url_ = 0 + self.url_ = "" + + def has_url(self): return self.has_url_ + + def header_size(self): return len(self.header_) + def header_list(self): return self.header_ + + def header(self, i): + return self.header_[i] + + def mutable_header(self, i): + return self.header_[i] + + def add_header(self): + x = URLFetchRequest_Header() + self.header_.append(x) + return x + + def clear_header(self): + self.header_ = [] + def payload(self): return self.payload_ + + def set_payload(self, x): + self.has_payload_ = 1 + self.payload_ = x + + def clear_payload(self): + if self.has_payload_: + self.has_payload_ = 0 + self.payload_ = "" + + def has_payload(self): return self.has_payload_ + + def followredirects(self): return self.followredirects_ + + def set_followredirects(self, x): + self.has_followredirects_ = 1 + self.followredirects_ = x + + def clear_followredirects(self): + if self.has_followredirects_: + self.has_followredirects_ = 0 + self.followredirects_ = 1 + + def has_followredirects(self): return self.has_followredirects_ + + def deadline(self): return self.deadline_ + + def set_deadline(self, x): + self.has_deadline_ = 1 + self.deadline_ = x + + def clear_deadline(self): + if self.has_deadline_: + self.has_deadline_ = 0 + self.deadline_ = 0.0 + + def has_deadline(self): return self.has_deadline_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_method()): self.set_method(x.method()) + if (x.has_url()): self.set_url(x.url()) + for i in xrange(x.header_size()): self.add_header().CopyFrom(x.header(i)) + if (x.has_payload()): self.set_payload(x.payload()) + if (x.has_followredirects()): self.set_followredirects(x.followredirects()) + if (x.has_deadline()): self.set_deadline(x.deadline()) + + def Equals(self, x): + if x is self: return 1 + if self.has_method_ != x.has_method_: return 0 + if self.has_method_ and self.method_ != x.method_: return 0 + if self.has_url_ != x.has_url_: return 0 + if self.has_url_ and self.url_ != x.url_: return 0 + if len(self.header_) != len(x.header_): return 0 + for e1, e2 in zip(self.header_, x.header_): + if e1 != e2: return 0 + if self.has_payload_ != x.has_payload_: return 0 + if self.has_payload_ and self.payload_ != x.payload_: return 0 + if self.has_followredirects_ != x.has_followredirects_: return 0 + if self.has_followredirects_ and self.followredirects_ != x.followredirects_: return 0 + if self.has_deadline_ != x.has_deadline_: return 0 + if self.has_deadline_ and self.deadline_ != x.deadline_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_method_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: method not set.') + if (not self.has_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: url not set.') + for p in self.header_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.method_) + n += self.lengthString(len(self.url_)) + n += 2 * len(self.header_) + for i in xrange(len(self.header_)): n += self.header_[i].ByteSize() + if (self.has_payload_): n += 1 + self.lengthString(len(self.payload_)) + if (self.has_followredirects_): n += 2 + if (self.has_deadline_): n += 9 + return n + 2 + + def Clear(self): + self.clear_method() + self.clear_url() + self.clear_header() + self.clear_payload() + self.clear_followredirects() + self.clear_deadline() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.method_) + out.putVarInt32(18) + out.putPrefixedString(self.url_) + for i in xrange(len(self.header_)): + out.putVarInt32(27) + self.header_[i].OutputUnchecked(out) + out.putVarInt32(28) + if (self.has_payload_): + out.putVarInt32(50) + out.putPrefixedString(self.payload_) + if (self.has_followredirects_): + out.putVarInt32(56) + out.putBoolean(self.followredirects_) + if (self.has_deadline_): + out.putVarInt32(65) + out.putDouble(self.deadline_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_method(d.getVarInt32()) + continue + if tt == 18: + self.set_url(d.getPrefixedString()) + continue + if tt == 27: + self.add_header().TryMerge(d) + continue + if tt == 50: + self.set_payload(d.getPrefixedString()) + continue + if tt == 56: + self.set_followredirects(d.getBoolean()) + continue + if tt == 65: + self.set_deadline(d.getDouble()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_method_: res+=prefix+("Method: %s\n" % self.DebugFormatInt32(self.method_)) + if self.has_url_: res+=prefix+("Url: %s\n" % self.DebugFormatString(self.url_)) + cnt=0 + for e in self.header_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Header%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_payload_: res+=prefix+("Payload: %s\n" % self.DebugFormatString(self.payload_)) + if self.has_followredirects_: res+=prefix+("FollowRedirects: %s\n" % self.DebugFormatBool(self.followredirects_)) + if self.has_deadline_: res+=prefix+("Deadline: %s\n" % self.DebugFormat(self.deadline_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kMethod = 1 + kUrl = 2 + kHeaderGroup = 3 + kHeaderKey = 4 + kHeaderValue = 5 + kPayload = 6 + kFollowRedirects = 7 + kDeadline = 8 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Method", + 2: "Url", + 3: "Header", + 4: "Key", + 5: "Value", + 6: "Payload", + 7: "FollowRedirects", + 8: "Deadline", + }, 8) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STARTGROUP, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + 7: ProtocolBuffer.Encoder.NUMERIC, + 8: ProtocolBuffer.Encoder.DOUBLE, + }, 8, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class URLFetchResponse_Header(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(34) + out.putPrefixedString(self.key_) + out.putVarInt32(42) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 28: break + if tt == 34: + self.set_key(d.getPrefixedString()) + continue + if tt == 42: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("Key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("Value: %s\n" % self.DebugFormatString(self.value_)) + return res + +class URLFetchResponse(ProtocolBuffer.ProtocolMessage): + has_content_ = 0 + content_ = "" + has_statuscode_ = 0 + statuscode_ = 0 + has_contentwastruncated_ = 0 + contentwastruncated_ = 0 + has_externalbytessent_ = 0 + externalbytessent_ = 0 + has_externalbytesreceived_ = 0 + externalbytesreceived_ = 0 + has_finalurl_ = 0 + finalurl_ = "" + has_apicpumilliseconds_ = 0 + apicpumilliseconds_ = 0 + has_apibytessent_ = 0 + apibytessent_ = 0 + has_apibytesreceived_ = 0 + apibytesreceived_ = 0 + + def __init__(self, contents=None): + self.header_ = [] + if contents is not None: self.MergeFromString(contents) + + def content(self): return self.content_ + + def set_content(self, x): + self.has_content_ = 1 + self.content_ = x + + def clear_content(self): + if self.has_content_: + self.has_content_ = 0 + self.content_ = "" + + def has_content(self): return self.has_content_ + + def statuscode(self): return self.statuscode_ + + def set_statuscode(self, x): + self.has_statuscode_ = 1 + self.statuscode_ = x + + def clear_statuscode(self): + if self.has_statuscode_: + self.has_statuscode_ = 0 + self.statuscode_ = 0 + + def has_statuscode(self): return self.has_statuscode_ + + def header_size(self): return len(self.header_) + def header_list(self): return self.header_ + + def header(self, i): + return self.header_[i] + + def mutable_header(self, i): + return self.header_[i] + + def add_header(self): + x = URLFetchResponse_Header() + self.header_.append(x) + return x + + def clear_header(self): + self.header_ = [] + def contentwastruncated(self): return self.contentwastruncated_ + + def set_contentwastruncated(self, x): + self.has_contentwastruncated_ = 1 + self.contentwastruncated_ = x + + def clear_contentwastruncated(self): + if self.has_contentwastruncated_: + self.has_contentwastruncated_ = 0 + self.contentwastruncated_ = 0 + + def has_contentwastruncated(self): return self.has_contentwastruncated_ + + def externalbytessent(self): return self.externalbytessent_ + + def set_externalbytessent(self, x): + self.has_externalbytessent_ = 1 + self.externalbytessent_ = x + + def clear_externalbytessent(self): + if self.has_externalbytessent_: + self.has_externalbytessent_ = 0 + self.externalbytessent_ = 0 + + def has_externalbytessent(self): return self.has_externalbytessent_ + + def externalbytesreceived(self): return self.externalbytesreceived_ + + def set_externalbytesreceived(self, x): + self.has_externalbytesreceived_ = 1 + self.externalbytesreceived_ = x + + def clear_externalbytesreceived(self): + if self.has_externalbytesreceived_: + self.has_externalbytesreceived_ = 0 + self.externalbytesreceived_ = 0 + + def has_externalbytesreceived(self): return self.has_externalbytesreceived_ + + def finalurl(self): return self.finalurl_ + + def set_finalurl(self, x): + self.has_finalurl_ = 1 + self.finalurl_ = x + + def clear_finalurl(self): + if self.has_finalurl_: + self.has_finalurl_ = 0 + self.finalurl_ = "" + + def has_finalurl(self): return self.has_finalurl_ + + def apicpumilliseconds(self): return self.apicpumilliseconds_ + + def set_apicpumilliseconds(self, x): + self.has_apicpumilliseconds_ = 1 + self.apicpumilliseconds_ = x + + def clear_apicpumilliseconds(self): + if self.has_apicpumilliseconds_: + self.has_apicpumilliseconds_ = 0 + self.apicpumilliseconds_ = 0 + + def has_apicpumilliseconds(self): return self.has_apicpumilliseconds_ + + def apibytessent(self): return self.apibytessent_ + + def set_apibytessent(self, x): + self.has_apibytessent_ = 1 + self.apibytessent_ = x + + def clear_apibytessent(self): + if self.has_apibytessent_: + self.has_apibytessent_ = 0 + self.apibytessent_ = 0 + + def has_apibytessent(self): return self.has_apibytessent_ + + def apibytesreceived(self): return self.apibytesreceived_ + + def set_apibytesreceived(self, x): + self.has_apibytesreceived_ = 1 + self.apibytesreceived_ = x + + def clear_apibytesreceived(self): + if self.has_apibytesreceived_: + self.has_apibytesreceived_ = 0 + self.apibytesreceived_ = 0 + + def has_apibytesreceived(self): return self.has_apibytesreceived_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_content()): self.set_content(x.content()) + if (x.has_statuscode()): self.set_statuscode(x.statuscode()) + for i in xrange(x.header_size()): self.add_header().CopyFrom(x.header(i)) + if (x.has_contentwastruncated()): self.set_contentwastruncated(x.contentwastruncated()) + if (x.has_externalbytessent()): self.set_externalbytessent(x.externalbytessent()) + if (x.has_externalbytesreceived()): self.set_externalbytesreceived(x.externalbytesreceived()) + if (x.has_finalurl()): self.set_finalurl(x.finalurl()) + if (x.has_apicpumilliseconds()): self.set_apicpumilliseconds(x.apicpumilliseconds()) + if (x.has_apibytessent()): self.set_apibytessent(x.apibytessent()) + if (x.has_apibytesreceived()): self.set_apibytesreceived(x.apibytesreceived()) + + def Equals(self, x): + if x is self: return 1 + if self.has_content_ != x.has_content_: return 0 + if self.has_content_ and self.content_ != x.content_: return 0 + if self.has_statuscode_ != x.has_statuscode_: return 0 + if self.has_statuscode_ and self.statuscode_ != x.statuscode_: return 0 + if len(self.header_) != len(x.header_): return 0 + for e1, e2 in zip(self.header_, x.header_): + if e1 != e2: return 0 + if self.has_contentwastruncated_ != x.has_contentwastruncated_: return 0 + if self.has_contentwastruncated_ and self.contentwastruncated_ != x.contentwastruncated_: return 0 + if self.has_externalbytessent_ != x.has_externalbytessent_: return 0 + if self.has_externalbytessent_ and self.externalbytessent_ != x.externalbytessent_: return 0 + if self.has_externalbytesreceived_ != x.has_externalbytesreceived_: return 0 + if self.has_externalbytesreceived_ and self.externalbytesreceived_ != x.externalbytesreceived_: return 0 + if self.has_finalurl_ != x.has_finalurl_: return 0 + if self.has_finalurl_ and self.finalurl_ != x.finalurl_: return 0 + if self.has_apicpumilliseconds_ != x.has_apicpumilliseconds_: return 0 + if self.has_apicpumilliseconds_ and self.apicpumilliseconds_ != x.apicpumilliseconds_: return 0 + if self.has_apibytessent_ != x.has_apibytessent_: return 0 + if self.has_apibytessent_ and self.apibytessent_ != x.apibytessent_: return 0 + if self.has_apibytesreceived_ != x.has_apibytesreceived_: return 0 + if self.has_apibytesreceived_ and self.apibytesreceived_ != x.apibytesreceived_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_statuscode_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: statuscode not set.') + for p in self.header_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_content_): n += 1 + self.lengthString(len(self.content_)) + n += self.lengthVarInt64(self.statuscode_) + n += 2 * len(self.header_) + for i in xrange(len(self.header_)): n += self.header_[i].ByteSize() + if (self.has_contentwastruncated_): n += 2 + if (self.has_externalbytessent_): n += 1 + self.lengthVarInt64(self.externalbytessent_) + if (self.has_externalbytesreceived_): n += 1 + self.lengthVarInt64(self.externalbytesreceived_) + if (self.has_finalurl_): n += 1 + self.lengthString(len(self.finalurl_)) + if (self.has_apicpumilliseconds_): n += 1 + self.lengthVarInt64(self.apicpumilliseconds_) + if (self.has_apibytessent_): n += 1 + self.lengthVarInt64(self.apibytessent_) + if (self.has_apibytesreceived_): n += 1 + self.lengthVarInt64(self.apibytesreceived_) + return n + 1 + + def Clear(self): + self.clear_content() + self.clear_statuscode() + self.clear_header() + self.clear_contentwastruncated() + self.clear_externalbytessent() + self.clear_externalbytesreceived() + self.clear_finalurl() + self.clear_apicpumilliseconds() + self.clear_apibytessent() + self.clear_apibytesreceived() + + def OutputUnchecked(self, out): + if (self.has_content_): + out.putVarInt32(10) + out.putPrefixedString(self.content_) + out.putVarInt32(16) + out.putVarInt32(self.statuscode_) + for i in xrange(len(self.header_)): + out.putVarInt32(27) + self.header_[i].OutputUnchecked(out) + out.putVarInt32(28) + if (self.has_contentwastruncated_): + out.putVarInt32(48) + out.putBoolean(self.contentwastruncated_) + if (self.has_externalbytessent_): + out.putVarInt32(56) + out.putVarInt64(self.externalbytessent_) + if (self.has_externalbytesreceived_): + out.putVarInt32(64) + out.putVarInt64(self.externalbytesreceived_) + if (self.has_finalurl_): + out.putVarInt32(74) + out.putPrefixedString(self.finalurl_) + if (self.has_apicpumilliseconds_): + out.putVarInt32(80) + out.putVarInt64(self.apicpumilliseconds_) + if (self.has_apibytessent_): + out.putVarInt32(88) + out.putVarInt64(self.apibytessent_) + if (self.has_apibytesreceived_): + out.putVarInt32(96) + out.putVarInt64(self.apibytesreceived_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_content(d.getPrefixedString()) + continue + if tt == 16: + self.set_statuscode(d.getVarInt32()) + continue + if tt == 27: + self.add_header().TryMerge(d) + continue + if tt == 48: + self.set_contentwastruncated(d.getBoolean()) + continue + if tt == 56: + self.set_externalbytessent(d.getVarInt64()) + continue + if tt == 64: + self.set_externalbytesreceived(d.getVarInt64()) + continue + if tt == 74: + self.set_finalurl(d.getPrefixedString()) + continue + if tt == 80: + self.set_apicpumilliseconds(d.getVarInt64()) + continue + if tt == 88: + self.set_apibytessent(d.getVarInt64()) + continue + if tt == 96: + self.set_apibytesreceived(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_content_: res+=prefix+("Content: %s\n" % self.DebugFormatString(self.content_)) + if self.has_statuscode_: res+=prefix+("StatusCode: %s\n" % self.DebugFormatInt32(self.statuscode_)) + cnt=0 + for e in self.header_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Header%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_contentwastruncated_: res+=prefix+("ContentWasTruncated: %s\n" % self.DebugFormatBool(self.contentwastruncated_)) + if self.has_externalbytessent_: res+=prefix+("ExternalBytesSent: %s\n" % self.DebugFormatInt64(self.externalbytessent_)) + if self.has_externalbytesreceived_: res+=prefix+("ExternalBytesReceived: %s\n" % self.DebugFormatInt64(self.externalbytesreceived_)) + if self.has_finalurl_: res+=prefix+("FinalUrl: %s\n" % self.DebugFormatString(self.finalurl_)) + if self.has_apicpumilliseconds_: res+=prefix+("ApiCpuMilliseconds: %s\n" % self.DebugFormatInt64(self.apicpumilliseconds_)) + if self.has_apibytessent_: res+=prefix+("ApiBytesSent: %s\n" % self.DebugFormatInt64(self.apibytessent_)) + if self.has_apibytesreceived_: res+=prefix+("ApiBytesReceived: %s\n" % self.DebugFormatInt64(self.apibytesreceived_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kContent = 1 + kStatusCode = 2 + kHeaderGroup = 3 + kHeaderKey = 4 + kHeaderValue = 5 + kContentWasTruncated = 6 + kExternalBytesSent = 7 + kExternalBytesReceived = 8 + kFinalUrl = 9 + kApiCpuMilliseconds = 10 + kApiBytesSent = 11 + kApiBytesReceived = 12 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Content", + 2: "StatusCode", + 3: "Header", + 4: "Key", + 5: "Value", + 6: "ContentWasTruncated", + 7: "ExternalBytesSent", + 8: "ExternalBytesReceived", + 9: "FinalUrl", + 10: "ApiCpuMilliseconds", + 11: "ApiBytesSent", + 12: "ApiBytesReceived", + }, 12) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STARTGROUP, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.NUMERIC, + 7: ProtocolBuffer.Encoder.NUMERIC, + 8: ProtocolBuffer.Encoder.NUMERIC, + 9: ProtocolBuffer.Encoder.STRING, + 10: ProtocolBuffer.Encoder.NUMERIC, + 11: ProtocolBuffer.Encoder.NUMERIC, + 12: ProtocolBuffer.Encoder.NUMERIC, + }, 12, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['URLFetchServiceError','URLFetchRequest','URLFetchRequest_Header','URLFetchResponse','URLFetchResponse_Header'] diff --git a/google_appengine/google/appengine/api/urlfetch_service_pb.pyc b/google_appengine/google/appengine/api/urlfetch_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a217f2a3f73d39a59512193c88f38abfe975a8b2 GIT binary patch literal 43800 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/urlfetch_stub.py b/google_appengine/google/appengine/api/urlfetch_stub.py new file mode 100755 index 0000000..a7775f2 --- /dev/null +++ b/google_appengine/google/appengine/api/urlfetch_stub.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the urlfetch API, based on httplib.""" + + + +import gzip +import httplib +import logging +import socket +import StringIO +import urllib +import urlparse + +from google.appengine.api import apiproxy_stub +from google.appengine.api import urlfetch +from google.appengine.api import urlfetch_errors +from google.appengine.api import urlfetch_service_pb +from google.appengine.runtime import apiproxy_errors + + +MAX_RESPONSE_SIZE = 2 ** 24 + +MAX_REDIRECTS = urlfetch.MAX_REDIRECTS + +REDIRECT_STATUSES = frozenset([ + httplib.MOVED_PERMANENTLY, + httplib.FOUND, + httplib.SEE_OTHER, + httplib.TEMPORARY_REDIRECT, +]) + +_API_CALL_DEADLINE = 5.0 + + +_UNTRUSTED_REQUEST_HEADERS = frozenset([ + 'content-length', + 'host', + 'vary', + 'via', + 'x-forwarded-for', +]) + + +def _IsAllowedPort(port): + if port is None: + return True + try: + port = int(port) + except ValueError, e: + return False + if ((port >= 80 and port <= 90) or + (port >= 440 and port <= 450) or + port >= 1024): + return True + return False + + +class URLFetchServiceStub(apiproxy_stub.APIProxyStub): + """Stub version of the urlfetch API to be used with apiproxy_stub_map.""" + + def __init__(self, service_name='urlfetch'): + """Initializer. + + Args: + service_name: Service name expected for all calls. + """ + super(URLFetchServiceStub, self).__init__(service_name) + + def _Dynamic_Fetch(self, request, response): + """Trivial implementation of URLFetchService::Fetch(). + + Args: + request: the fetch to perform, a URLFetchRequest + response: the fetch response, a URLFetchResponse + """ + (protocol, host, path, parameters, query, fragment) = urlparse.urlparse(request.url()) + + payload = None + if request.method() == urlfetch_service_pb.URLFetchRequest.GET: + method = 'GET' + elif request.method() == urlfetch_service_pb.URLFetchRequest.POST: + method = 'POST' + payload = request.payload() + elif request.method() == urlfetch_service_pb.URLFetchRequest.HEAD: + method = 'HEAD' + elif request.method() == urlfetch_service_pb.URLFetchRequest.PUT: + method = 'PUT' + payload = request.payload() + elif request.method() == urlfetch_service_pb.URLFetchRequest.DELETE: + method = 'DELETE' + else: + logging.error('Invalid method: %s', request.method()) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.UNSPECIFIED_ERROR) + + if not (protocol == 'http' or protocol == 'https'): + logging.error('Invalid protocol: %s', protocol) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.INVALID_URL) + + if not host: + logging.error('Missing host.') + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR) + + sanitized_headers = self._SanitizeHttpHeaders(_UNTRUSTED_REQUEST_HEADERS, + request.header_list()) + request.clear_header() + request.header_list().extend(sanitized_headers) + deadline = _API_CALL_DEADLINE + if request.has_deadline(): + deadline = request.deadline() + + self._RetrieveURL(request.url(), payload, method, + request.header_list(), request, response, + follow_redirects=request.followredirects(), + deadline=deadline) + + def _RetrieveURL(self, url, payload, method, headers, request, response, + follow_redirects=True, deadline=_API_CALL_DEADLINE): + """Retrieves a URL. + + Args: + url: String containing the URL to access. + payload: Request payload to send, if any; None if no payload. + method: HTTP method to use (e.g., 'GET') + headers: List of additional header objects to use for the request. + request: Request object from original request. + response: Response object to populate with the response data. + follow_redirects: optional setting (defaulting to True) for whether or not + we should transparently follow redirects (up to MAX_REDIRECTS) + deadline: Number of seconds to wait for the urlfetch to finish. + + Raises: + Raises an apiproxy_errors.ApplicationError exception with FETCH_ERROR + in cases where: + - MAX_REDIRECTS is exceeded + - The protocol of the redirected URL is bad or missing. + """ + last_protocol = '' + last_host = '' + + for redirect_number in xrange(MAX_REDIRECTS + 1): + parsed = urlparse.urlparse(url) + protocol, host, path, parameters, query, fragment = parsed + + port = urllib.splitport(urllib.splituser(host)[1])[1] + + if not _IsAllowedPort(port): + logging.warning( + 'urlfetch received %s ; port %s is not allowed in production!' % + (url, port)) + + if protocol and not host: + logging.error('Missing host on redirect; target url is %s' % url) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR) + + if not host and not protocol: + host = last_host + protocol = last_protocol + + adjusted_headers = { + 'User-Agent': + 'AppEngine-Google; (+http://code.google.com/appengine)', + 'Host': host, + 'Accept-Encoding': 'gzip', + } + if payload is not None: + adjusted_headers['Content-Length'] = len(payload) + if method == 'POST' and payload: + adjusted_headers['Content-Type'] = 'application/x-www-form-urlencoded' + + for header in headers: + if header.key().title().lower() == 'user-agent': + adjusted_headers['User-Agent'] = ( + '%s %s' % + (header.value(), adjusted_headers['User-Agent'])) + else: + adjusted_headers[header.key().title()] = header.value() + + logging.debug('Making HTTP request: host = %s, ' + 'url = %s, payload = %s, headers = %s', + host, url, payload, adjusted_headers) + try: + if protocol == 'http': + connection = httplib.HTTPConnection(host) + elif protocol == 'https': + connection = httplib.HTTPSConnection(host) + else: + error_msg = 'Redirect specified invalid protocol: "%s"' % protocol + logging.error(error_msg) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg) + + last_protocol = protocol + last_host = host + + if query != '': + full_path = path + '?' + query + else: + full_path = path + + orig_timeout = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(deadline) + connection.request(method, full_path, payload, adjusted_headers) + http_response = connection.getresponse() + if method == 'HEAD': + http_response_data = '' + else: + http_response_data = http_response.read() + finally: + socket.setdefaulttimeout(orig_timeout) + connection.close() + except (httplib.error, socket.error, IOError), e: + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, str(e)) + + if http_response.status in REDIRECT_STATUSES and follow_redirects: + url = http_response.getheader('Location', None) + if url is None: + error_msg = 'Redirecting response was missing "Location" header' + logging.error(error_msg) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg) + else: + response.set_statuscode(http_response.status) + if http_response.getheader('content-encoding') == 'gzip': + gzip_stream = StringIO.StringIO(http_response_data) + gzip_file = gzip.GzipFile(fileobj=gzip_stream) + http_response_data = gzip_file.read() + response.set_content(http_response_data[:MAX_RESPONSE_SIZE]) + for header_key, header_value in http_response.getheaders(): + if (header_key.lower() == 'content-encoding' and + header_value == 'gzip'): + continue + if header_key.lower() == 'content-length': + header_value = str(len(response.content())) + header_proto = response.add_header() + header_proto.set_key(header_key) + header_proto.set_value(header_value) + + if len(http_response_data) > MAX_RESPONSE_SIZE: + response.set_contentwastruncated(True) + + if request.url() != url: + response.set_finalurl(url) + + break + else: + error_msg = 'Too many repeated redirects' + logging.error(error_msg) + raise apiproxy_errors.ApplicationError( + urlfetch_service_pb.URLFetchServiceError.FETCH_ERROR, error_msg) + + def _SanitizeHttpHeaders(self, untrusted_headers, headers): + """Cleans "unsafe" headers from the HTTP request/response. + + Args: + untrusted_headers: set of untrusted headers names + headers: list of string pairs, first is header name and the second is header's value + """ + prohibited_headers = [h.key() for h in headers + if h.key().lower() in untrusted_headers] + if prohibited_headers: + logging.warn('Stripped prohibited headers from URLFetch request: %s', + prohibited_headers) + return (h for h in headers if h.key().lower() not in untrusted_headers) diff --git a/google_appengine/google/appengine/api/urlfetch_stub.pyc b/google_appengine/google/appengine/api/urlfetch_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e910a92fca17795e449cbd5577c4ee49a59f0256 GIT binary patch literal 8749 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/user_service_pb.py b/google_appengine/google/appengine/api/user_service_pb.py new file mode 100755 index 0000000..46b4d73 --- /dev/null +++ b/google_appengine/google/appengine/api/user_service_pb.py @@ -0,0 +1,1329 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.api.api_base_pb import * +import google.appengine.api.api_base_pb +class UserServiceError(ProtocolBuffer.ProtocolMessage): + + OK = 0 + REDIRECT_URL_TOO_LONG = 1 + NOT_ALLOWED = 2 + OAUTH_INVALID_TOKEN = 3 + OAUTH_INVALID_REQUEST = 4 + OAUTH_ERROR = 5 + + _ErrorCode_NAMES = { + 0: "OK", + 1: "REDIRECT_URL_TOO_LONG", + 2: "NOT_ALLOWED", + 3: "OAUTH_INVALID_TOKEN", + 4: "OAUTH_INVALID_REQUEST", + 5: "OAUTH_ERROR", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateLoginURLRequest(ProtocolBuffer.ProtocolMessage): + has_destination_url_ = 0 + destination_url_ = "" + has_auth_domain_ = 0 + auth_domain_ = "" + has_federated_identity_ = 0 + federated_identity_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def destination_url(self): return self.destination_url_ + + def set_destination_url(self, x): + self.has_destination_url_ = 1 + self.destination_url_ = x + + def clear_destination_url(self): + if self.has_destination_url_: + self.has_destination_url_ = 0 + self.destination_url_ = "" + + def has_destination_url(self): return self.has_destination_url_ + + def auth_domain(self): return self.auth_domain_ + + def set_auth_domain(self, x): + self.has_auth_domain_ = 1 + self.auth_domain_ = x + + def clear_auth_domain(self): + if self.has_auth_domain_: + self.has_auth_domain_ = 0 + self.auth_domain_ = "" + + def has_auth_domain(self): return self.has_auth_domain_ + + def federated_identity(self): return self.federated_identity_ + + def set_federated_identity(self, x): + self.has_federated_identity_ = 1 + self.federated_identity_ = x + + def clear_federated_identity(self): + if self.has_federated_identity_: + self.has_federated_identity_ = 0 + self.federated_identity_ = "" + + def has_federated_identity(self): return self.has_federated_identity_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_destination_url()): self.set_destination_url(x.destination_url()) + if (x.has_auth_domain()): self.set_auth_domain(x.auth_domain()) + if (x.has_federated_identity()): self.set_federated_identity(x.federated_identity()) + + def Equals(self, x): + if x is self: return 1 + if self.has_destination_url_ != x.has_destination_url_: return 0 + if self.has_destination_url_ and self.destination_url_ != x.destination_url_: return 0 + if self.has_auth_domain_ != x.has_auth_domain_: return 0 + if self.has_auth_domain_ and self.auth_domain_ != x.auth_domain_: return 0 + if self.has_federated_identity_ != x.has_federated_identity_: return 0 + if self.has_federated_identity_ and self.federated_identity_ != x.federated_identity_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_destination_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: destination_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.destination_url_)) + if (self.has_auth_domain_): n += 1 + self.lengthString(len(self.auth_domain_)) + if (self.has_federated_identity_): n += 1 + self.lengthString(len(self.federated_identity_)) + return n + 1 + + def Clear(self): + self.clear_destination_url() + self.clear_auth_domain() + self.clear_federated_identity() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.destination_url_) + if (self.has_auth_domain_): + out.putVarInt32(18) + out.putPrefixedString(self.auth_domain_) + if (self.has_federated_identity_): + out.putVarInt32(26) + out.putPrefixedString(self.federated_identity_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_destination_url(d.getPrefixedString()) + continue + if tt == 18: + self.set_auth_domain(d.getPrefixedString()) + continue + if tt == 26: + self.set_federated_identity(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_destination_url_: res+=prefix+("destination_url: %s\n" % self.DebugFormatString(self.destination_url_)) + if self.has_auth_domain_: res+=prefix+("auth_domain: %s\n" % self.DebugFormatString(self.auth_domain_)) + if self.has_federated_identity_: res+=prefix+("federated_identity: %s\n" % self.DebugFormatString(self.federated_identity_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdestination_url = 1 + kauth_domain = 2 + kfederated_identity = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "destination_url", + 2: "auth_domain", + 3: "federated_identity", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateLoginURLResponse(ProtocolBuffer.ProtocolMessage): + has_login_url_ = 0 + login_url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def login_url(self): return self.login_url_ + + def set_login_url(self, x): + self.has_login_url_ = 1 + self.login_url_ = x + + def clear_login_url(self): + if self.has_login_url_: + self.has_login_url_ = 0 + self.login_url_ = "" + + def has_login_url(self): return self.has_login_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_login_url()): self.set_login_url(x.login_url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_login_url_ != x.has_login_url_: return 0 + if self.has_login_url_ and self.login_url_ != x.login_url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_login_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: login_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.login_url_)) + return n + 1 + + def Clear(self): + self.clear_login_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.login_url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_login_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_login_url_: res+=prefix+("login_url: %s\n" % self.DebugFormatString(self.login_url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + klogin_url = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "login_url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateLogoutURLRequest(ProtocolBuffer.ProtocolMessage): + has_destination_url_ = 0 + destination_url_ = "" + has_auth_domain_ = 0 + auth_domain_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def destination_url(self): return self.destination_url_ + + def set_destination_url(self, x): + self.has_destination_url_ = 1 + self.destination_url_ = x + + def clear_destination_url(self): + if self.has_destination_url_: + self.has_destination_url_ = 0 + self.destination_url_ = "" + + def has_destination_url(self): return self.has_destination_url_ + + def auth_domain(self): return self.auth_domain_ + + def set_auth_domain(self, x): + self.has_auth_domain_ = 1 + self.auth_domain_ = x + + def clear_auth_domain(self): + if self.has_auth_domain_: + self.has_auth_domain_ = 0 + self.auth_domain_ = "" + + def has_auth_domain(self): return self.has_auth_domain_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_destination_url()): self.set_destination_url(x.destination_url()) + if (x.has_auth_domain()): self.set_auth_domain(x.auth_domain()) + + def Equals(self, x): + if x is self: return 1 + if self.has_destination_url_ != x.has_destination_url_: return 0 + if self.has_destination_url_ and self.destination_url_ != x.destination_url_: return 0 + if self.has_auth_domain_ != x.has_auth_domain_: return 0 + if self.has_auth_domain_ and self.auth_domain_ != x.auth_domain_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_destination_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: destination_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.destination_url_)) + if (self.has_auth_domain_): n += 1 + self.lengthString(len(self.auth_domain_)) + return n + 1 + + def Clear(self): + self.clear_destination_url() + self.clear_auth_domain() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.destination_url_) + if (self.has_auth_domain_): + out.putVarInt32(18) + out.putPrefixedString(self.auth_domain_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_destination_url(d.getPrefixedString()) + continue + if tt == 18: + self.set_auth_domain(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_destination_url_: res+=prefix+("destination_url: %s\n" % self.DebugFormatString(self.destination_url_)) + if self.has_auth_domain_: res+=prefix+("auth_domain: %s\n" % self.DebugFormatString(self.auth_domain_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdestination_url = 1 + kauth_domain = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "destination_url", + 2: "auth_domain", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateLogoutURLResponse(ProtocolBuffer.ProtocolMessage): + has_logout_url_ = 0 + logout_url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def logout_url(self): return self.logout_url_ + + def set_logout_url(self, x): + self.has_logout_url_ = 1 + self.logout_url_ = x + + def clear_logout_url(self): + if self.has_logout_url_: + self.has_logout_url_ = 0 + self.logout_url_ = "" + + def has_logout_url(self): return self.has_logout_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_logout_url()): self.set_logout_url(x.logout_url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_logout_url_ != x.has_logout_url_: return 0 + if self.has_logout_url_ and self.logout_url_ != x.logout_url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_logout_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: logout_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.logout_url_)) + return n + 1 + + def Clear(self): + self.clear_logout_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.logout_url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_logout_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_logout_url_: res+=prefix+("logout_url: %s\n" % self.DebugFormatString(self.logout_url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + klogout_url = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "logout_url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetOAuthUserRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetOAuthUserResponse(ProtocolBuffer.ProtocolMessage): + has_email_ = 0 + email_ = "" + has_user_id_ = 0 + user_id_ = "" + has_auth_domain_ = 0 + auth_domain_ = "" + has_user_organization_ = 0 + user_organization_ = "" + has_is_admin_ = 0 + is_admin_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def email(self): return self.email_ + + def set_email(self, x): + self.has_email_ = 1 + self.email_ = x + + def clear_email(self): + if self.has_email_: + self.has_email_ = 0 + self.email_ = "" + + def has_email(self): return self.has_email_ + + def user_id(self): return self.user_id_ + + def set_user_id(self, x): + self.has_user_id_ = 1 + self.user_id_ = x + + def clear_user_id(self): + if self.has_user_id_: + self.has_user_id_ = 0 + self.user_id_ = "" + + def has_user_id(self): return self.has_user_id_ + + def auth_domain(self): return self.auth_domain_ + + def set_auth_domain(self, x): + self.has_auth_domain_ = 1 + self.auth_domain_ = x + + def clear_auth_domain(self): + if self.has_auth_domain_: + self.has_auth_domain_ = 0 + self.auth_domain_ = "" + + def has_auth_domain(self): return self.has_auth_domain_ + + def user_organization(self): return self.user_organization_ + + def set_user_organization(self, x): + self.has_user_organization_ = 1 + self.user_organization_ = x + + def clear_user_organization(self): + if self.has_user_organization_: + self.has_user_organization_ = 0 + self.user_organization_ = "" + + def has_user_organization(self): return self.has_user_organization_ + + def is_admin(self): return self.is_admin_ + + def set_is_admin(self, x): + self.has_is_admin_ = 1 + self.is_admin_ = x + + def clear_is_admin(self): + if self.has_is_admin_: + self.has_is_admin_ = 0 + self.is_admin_ = 0 + + def has_is_admin(self): return self.has_is_admin_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_email()): self.set_email(x.email()) + if (x.has_user_id()): self.set_user_id(x.user_id()) + if (x.has_auth_domain()): self.set_auth_domain(x.auth_domain()) + if (x.has_user_organization()): self.set_user_organization(x.user_organization()) + if (x.has_is_admin()): self.set_is_admin(x.is_admin()) + + def Equals(self, x): + if x is self: return 1 + if self.has_email_ != x.has_email_: return 0 + if self.has_email_ and self.email_ != x.email_: return 0 + if self.has_user_id_ != x.has_user_id_: return 0 + if self.has_user_id_ and self.user_id_ != x.user_id_: return 0 + if self.has_auth_domain_ != x.has_auth_domain_: return 0 + if self.has_auth_domain_ and self.auth_domain_ != x.auth_domain_: return 0 + if self.has_user_organization_ != x.has_user_organization_: return 0 + if self.has_user_organization_ and self.user_organization_ != x.user_organization_: return 0 + if self.has_is_admin_ != x.has_is_admin_: return 0 + if self.has_is_admin_ and self.is_admin_ != x.is_admin_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_email_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: email not set.') + if (not self.has_user_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: user_id not set.') + if (not self.has_auth_domain_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: auth_domain not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.email_)) + n += self.lengthString(len(self.user_id_)) + n += self.lengthString(len(self.auth_domain_)) + if (self.has_user_organization_): n += 1 + self.lengthString(len(self.user_organization_)) + if (self.has_is_admin_): n += 2 + return n + 3 + + def Clear(self): + self.clear_email() + self.clear_user_id() + self.clear_auth_domain() + self.clear_user_organization() + self.clear_is_admin() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.email_) + out.putVarInt32(18) + out.putPrefixedString(self.user_id_) + out.putVarInt32(26) + out.putPrefixedString(self.auth_domain_) + if (self.has_user_organization_): + out.putVarInt32(34) + out.putPrefixedString(self.user_organization_) + if (self.has_is_admin_): + out.putVarInt32(40) + out.putBoolean(self.is_admin_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_email(d.getPrefixedString()) + continue + if tt == 18: + self.set_user_id(d.getPrefixedString()) + continue + if tt == 26: + self.set_auth_domain(d.getPrefixedString()) + continue + if tt == 34: + self.set_user_organization(d.getPrefixedString()) + continue + if tt == 40: + self.set_is_admin(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_email_: res+=prefix+("email: %s\n" % self.DebugFormatString(self.email_)) + if self.has_user_id_: res+=prefix+("user_id: %s\n" % self.DebugFormatString(self.user_id_)) + if self.has_auth_domain_: res+=prefix+("auth_domain: %s\n" % self.DebugFormatString(self.auth_domain_)) + if self.has_user_organization_: res+=prefix+("user_organization: %s\n" % self.DebugFormatString(self.user_organization_)) + if self.has_is_admin_: res+=prefix+("is_admin: %s\n" % self.DebugFormatBool(self.is_admin_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kemail = 1 + kuser_id = 2 + kauth_domain = 3 + kuser_organization = 4 + kis_admin = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "email", + 2: "user_id", + 3: "auth_domain", + 4: "user_organization", + 5: "is_admin", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CheckOAuthSignatureRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CheckOAuthSignatureResponse(ProtocolBuffer.ProtocolMessage): + has_oauth_consumer_key_ = 0 + oauth_consumer_key_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def oauth_consumer_key(self): return self.oauth_consumer_key_ + + def set_oauth_consumer_key(self, x): + self.has_oauth_consumer_key_ = 1 + self.oauth_consumer_key_ = x + + def clear_oauth_consumer_key(self): + if self.has_oauth_consumer_key_: + self.has_oauth_consumer_key_ = 0 + self.oauth_consumer_key_ = "" + + def has_oauth_consumer_key(self): return self.has_oauth_consumer_key_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_oauth_consumer_key()): self.set_oauth_consumer_key(x.oauth_consumer_key()) + + def Equals(self, x): + if x is self: return 1 + if self.has_oauth_consumer_key_ != x.has_oauth_consumer_key_: return 0 + if self.has_oauth_consumer_key_ and self.oauth_consumer_key_ != x.oauth_consumer_key_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_oauth_consumer_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: oauth_consumer_key not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.oauth_consumer_key_)) + return n + 1 + + def Clear(self): + self.clear_oauth_consumer_key() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.oauth_consumer_key_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_oauth_consumer_key(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_oauth_consumer_key_: res+=prefix+("oauth_consumer_key: %s\n" % self.DebugFormatString(self.oauth_consumer_key_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + koauth_consumer_key = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "oauth_consumer_key", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateFederatedLoginRequest(ProtocolBuffer.ProtocolMessage): + has_claimed_id_ = 0 + claimed_id_ = "" + has_continue_url_ = 0 + continue_url_ = "" + has_authority_ = 0 + authority_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def claimed_id(self): return self.claimed_id_ + + def set_claimed_id(self, x): + self.has_claimed_id_ = 1 + self.claimed_id_ = x + + def clear_claimed_id(self): + if self.has_claimed_id_: + self.has_claimed_id_ = 0 + self.claimed_id_ = "" + + def has_claimed_id(self): return self.has_claimed_id_ + + def continue_url(self): return self.continue_url_ + + def set_continue_url(self, x): + self.has_continue_url_ = 1 + self.continue_url_ = x + + def clear_continue_url(self): + if self.has_continue_url_: + self.has_continue_url_ = 0 + self.continue_url_ = "" + + def has_continue_url(self): return self.has_continue_url_ + + def authority(self): return self.authority_ + + def set_authority(self, x): + self.has_authority_ = 1 + self.authority_ = x + + def clear_authority(self): + if self.has_authority_: + self.has_authority_ = 0 + self.authority_ = "" + + def has_authority(self): return self.has_authority_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_claimed_id()): self.set_claimed_id(x.claimed_id()) + if (x.has_continue_url()): self.set_continue_url(x.continue_url()) + if (x.has_authority()): self.set_authority(x.authority()) + + def Equals(self, x): + if x is self: return 1 + if self.has_claimed_id_ != x.has_claimed_id_: return 0 + if self.has_claimed_id_ and self.claimed_id_ != x.claimed_id_: return 0 + if self.has_continue_url_ != x.has_continue_url_: return 0 + if self.has_continue_url_ and self.continue_url_ != x.continue_url_: return 0 + if self.has_authority_ != x.has_authority_: return 0 + if self.has_authority_ and self.authority_ != x.authority_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_claimed_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: claimed_id not set.') + if (not self.has_continue_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: continue_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.claimed_id_)) + n += self.lengthString(len(self.continue_url_)) + if (self.has_authority_): n += 1 + self.lengthString(len(self.authority_)) + return n + 2 + + def Clear(self): + self.clear_claimed_id() + self.clear_continue_url() + self.clear_authority() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.claimed_id_) + out.putVarInt32(18) + out.putPrefixedString(self.continue_url_) + if (self.has_authority_): + out.putVarInt32(26) + out.putPrefixedString(self.authority_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_claimed_id(d.getPrefixedString()) + continue + if tt == 18: + self.set_continue_url(d.getPrefixedString()) + continue + if tt == 26: + self.set_authority(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_claimed_id_: res+=prefix+("claimed_id: %s\n" % self.DebugFormatString(self.claimed_id_)) + if self.has_continue_url_: res+=prefix+("continue_url: %s\n" % self.DebugFormatString(self.continue_url_)) + if self.has_authority_: res+=prefix+("authority: %s\n" % self.DebugFormatString(self.authority_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kclaimed_id = 1 + kcontinue_url = 2 + kauthority = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "claimed_id", + 2: "continue_url", + 3: "authority", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateFederatedLoginResponse(ProtocolBuffer.ProtocolMessage): + has_redirected_url_ = 0 + redirected_url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def redirected_url(self): return self.redirected_url_ + + def set_redirected_url(self, x): + self.has_redirected_url_ = 1 + self.redirected_url_ = x + + def clear_redirected_url(self): + if self.has_redirected_url_: + self.has_redirected_url_ = 0 + self.redirected_url_ = "" + + def has_redirected_url(self): return self.has_redirected_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_redirected_url()): self.set_redirected_url(x.redirected_url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_redirected_url_ != x.has_redirected_url_: return 0 + if self.has_redirected_url_ and self.redirected_url_ != x.redirected_url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_redirected_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: redirected_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.redirected_url_)) + return n + 1 + + def Clear(self): + self.clear_redirected_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.redirected_url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_redirected_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_redirected_url_: res+=prefix+("redirected_url: %s\n" % self.DebugFormatString(self.redirected_url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kredirected_url = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "redirected_url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateFederatedLogoutRequest(ProtocolBuffer.ProtocolMessage): + has_destination_url_ = 0 + destination_url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def destination_url(self): return self.destination_url_ + + def set_destination_url(self, x): + self.has_destination_url_ = 1 + self.destination_url_ = x + + def clear_destination_url(self): + if self.has_destination_url_: + self.has_destination_url_ = 0 + self.destination_url_ = "" + + def has_destination_url(self): return self.has_destination_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_destination_url()): self.set_destination_url(x.destination_url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_destination_url_ != x.has_destination_url_: return 0 + if self.has_destination_url_ and self.destination_url_ != x.destination_url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_destination_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: destination_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.destination_url_)) + return n + 1 + + def Clear(self): + self.clear_destination_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.destination_url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_destination_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_destination_url_: res+=prefix+("destination_url: %s\n" % self.DebugFormatString(self.destination_url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kdestination_url = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "destination_url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CreateFederatedLogoutResponse(ProtocolBuffer.ProtocolMessage): + has_logout_url_ = 0 + logout_url_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def logout_url(self): return self.logout_url_ + + def set_logout_url(self, x): + self.has_logout_url_ = 1 + self.logout_url_ = x + + def clear_logout_url(self): + if self.has_logout_url_: + self.has_logout_url_ = 0 + self.logout_url_ = "" + + def has_logout_url(self): return self.has_logout_url_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_logout_url()): self.set_logout_url(x.logout_url()) + + def Equals(self, x): + if x is self: return 1 + if self.has_logout_url_ != x.has_logout_url_: return 0 + if self.has_logout_url_ and self.logout_url_ != x.logout_url_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_logout_url_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: logout_url not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.logout_url_)) + return n + 1 + + def Clear(self): + self.clear_logout_url() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.logout_url_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_logout_url(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_logout_url_: res+=prefix+("logout_url: %s\n" % self.DebugFormatString(self.logout_url_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + klogout_url = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "logout_url", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['UserServiceError','CreateLoginURLRequest','CreateLoginURLResponse','CreateLogoutURLRequest','CreateLogoutURLResponse','GetOAuthUserRequest','GetOAuthUserResponse','CheckOAuthSignatureRequest','CheckOAuthSignatureResponse','CreateFederatedLoginRequest','CreateFederatedLoginResponse','CreateFederatedLogoutRequest','CreateFederatedLogoutResponse'] diff --git a/google_appengine/google/appengine/api/user_service_pb.pyc b/google_appengine/google/appengine/api/user_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88fb1ba3adf0941b2938ce25d54ffbcdedce29f5 GIT binary patch literal 61454 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 B_9p-U literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/users.py b/google_appengine/google/appengine/api/users.py new file mode 100755 index 0000000..b2e2b4c --- /dev/null +++ b/google_appengine/google/appengine/api/users.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Python datastore class User to be used as a datastore data type. + +Classes defined here: + User: object representing a user. A user could be a Google Accounts user + or a federated user. + Error: base exception type + UserNotFoundError: UserService exception + RedirectTooLongError: UserService exception + NotAllowedError: UserService exception +""" + + + + + +import os +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import user_service_pb +from google.appengine.runtime import apiproxy_errors + + +class Error(Exception): + """Base User error type.""" + + +class UserNotFoundError(Error): + """Raised by User.__init__() when there's no email argument and no user is + logged in.""" + + +class RedirectTooLongError(Error): + """Raised by UserService calls if the generated redirect URL was too long. + """ + + +class NotAllowedError(Error): + """Raised by UserService calls if the requested redirect URL is not allowed. + """ + + +class User(object): + """A user. + + We provide the email address, nickname, and id for a user. + + A nickname is a human-readable string which uniquely identifies a Google + user, akin to a username. It will be an email address for some users, but + not all. + + A user could be a Google Accounts user or a federated login user. + + federated_identity and federated_provider are only avaliable for + federated users. + """ + + + __user_id = None + __federated_identity = None + __federated_provider = None + + def __init__(self, email=None, _auth_domain=None, + _user_id=None, federated_identity=None, federated_provider=None): + """Constructor. + + Args: + email: An optional string of the user's email address. It defaults to + the current user's email address. + federated_identity: federated identity of user. It defaults to the current + user's federated identity. + federated_provider: federated provider url of user. + + Raises: + UserNotFoundError: Raised if the user is not logged in and both email + and federated identity are empty. + """ + if _auth_domain is None: + _auth_domain = os.environ.get('AUTH_DOMAIN') + assert _auth_domain + + if email is None and federated_identity is None: + email = os.environ.get('USER_EMAIL', email) + _user_id = os.environ.get('USER_ID', _user_id) + federated_identity = os.environ.get('FEDERATED_IDENTITY', + federated_identity) + federated_provider = os.environ.get('FEDERATED_PROVIDER', + federated_provider) + + if not email and not federated_identity: + raise UserNotFoundError + + self.__email = email + self.__federated_identity = federated_identity + self.__federated_provider = federated_provider + self.__auth_domain = _auth_domain + self.__user_id = _user_id or None + + def nickname(self): + """Return this user's nickname. + + The nickname will be a unique, human readable identifier for this user + with respect to this application. It will be an email address for some + users, part of the email address for some users, and the federated identity + for federated users who have not asserted an email address. + """ + if (self.__email and self.__auth_domain and + self.__email.endswith('@' + self.__auth_domain)): + suffix_len = len(self.__auth_domain) + 1 + return self.__email[:-suffix_len] + elif self.__federated_identity: + return self.__federated_identity + else: + return self.__email + + def email(self): + """Return this user's email address.""" + return self.__email + + def user_id(self): + """Return either a permanent unique identifying string or None. + + If the email address was set explicity, this will return None. + """ + return self.__user_id + + def auth_domain(self): + """Return this user's auth domain. + + This method is internal and should not be used by client applications. + """ + return self.__auth_domain + + def federated_identity(self): + """Return this user's federated identity, None if not a federated user.""" + return self.__federated_identity + + def federated_provider(self): + """Return this user's federated provider, None if not a federated user.""" + return self.__federated_provider + + def __unicode__(self): + return unicode(self.nickname()) + + def __str__(self): + return str(self.nickname()) + + def __repr__(self): + values = [] + if self.__email: + values.append("email='%s'" % self.__email) + if self.__federated_identity: + values.append("federated_identity='%s'" % self.__federated_identity) + if self.__user_id: + values.append("_user_id='%s'" % self.__user_id) + return 'users.User(%s)' % ','.join(values) + + def __hash__(self): + if self.__federated_identity: + return hash((self.__federated_identity, self.__auth_domain)) + else: + return hash((self.__email, self.__auth_domain)) + + def __cmp__(self, other): + if not isinstance(other, User): + return NotImplemented + if self.__federated_identity: + return cmp((self.__federated_identity, self.__auth_domain), + (other.__federated_identity, other.__auth_domain)) + else: + return cmp((self.__email, self.__auth_domain), + (other.__email, other.__auth_domain)) + + +def create_login_url(dest_url=None, _auth_domain=None, + federated_identity=None): + """Computes the login URL for redirection. + + Args: + dest_url: String that is the desired final destination URL for the user + once login is complete. If 'dest_url' does not have a host + specified, we will use the host from the current request. + federated_identity: federated_identity is used to trigger OpenId Login + flow, an empty value will trigger Google OpenID Login + by default. + + Returns: + Login URL as a string. If federated_identity is set, this will be + a federated login using the specified identity. If not, this + will use Google Accounts. + """ + req = user_service_pb.CreateLoginURLRequest() + resp = user_service_pb.CreateLoginURLResponse() + req.set_destination_url(dest_url) + if _auth_domain: + req.set_auth_domain(_auth_domain) + if federated_identity: + req.set_federated_identity(federated_identity) + + try: + apiproxy_stub_map.MakeSyncCall('user', 'CreateLoginURL', req, resp) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + user_service_pb.UserServiceError.REDIRECT_URL_TOO_LONG): + raise RedirectTooLongError + elif (e.application_error == + user_service_pb.UserServiceError.NOT_ALLOWED): + raise NotAllowedError + else: + raise e + return resp.login_url() + +CreateLoginURL = create_login_url + + +def create_logout_url(dest_url, _auth_domain=None): + """Computes the logout URL for this request and specified destination URL, + for both federated login App and Google Accounts App. + + Args: + dest_url: String that is the desired final destination URL for the user + once logout is complete. If 'dest_url' does not have a host + specified, we will use the host from the current request. + + Returns: + Logout URL as a string + """ + req = user_service_pb.CreateLogoutURLRequest() + resp = user_service_pb.CreateLogoutURLResponse() + req.set_destination_url(dest_url) + if _auth_domain: + req.set_auth_domain(_auth_domain) + + try: + apiproxy_stub_map.MakeSyncCall('user', 'CreateLogoutURL', req, resp) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + user_service_pb.UserServiceError.REDIRECT_URL_TOO_LONG): + raise RedirectTooLongError + else: + raise e + return resp.logout_url() + +CreateLogoutURL = create_logout_url + + +def get_current_user(): + try: + return User() + except UserNotFoundError: + return None + +GetCurrentUser = get_current_user + + +def is_current_user_admin(): + """Return true if the user making this request is an admin for this + application, false otherwise. + + We specifically make this a separate function, and not a member function of + the User class, because admin status is not persisted in the datastore. It + only exists for the user making this request right now. + """ + return (os.environ.get('USER_IS_ADMIN', '0')) == '1' + +IsCurrentUserAdmin = is_current_user_admin diff --git a/google_appengine/google/appengine/api/users.pyc b/google_appengine/google/appengine/api/users.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c9d72424af69ed89f8a58032e09b99975e7a853 GIT binary patch literal 10537 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 Regex + type -> Type + collection -> Options + Validator -> Its self! + + Args: + validator: Object to wrap in a validator. + + Returns: + Validator instance that wraps the given value. + + Raises: + AttributeDefinitionError if validator is not one of the above described + types. + """ + if isinstance(validator, (str, unicode)): + return Regex(validator, type(validator)) + if isinstance(validator, type): + return Type(validator) + if isinstance(validator, (list, tuple, set)): + return Options(*tuple(validator)) + if isinstance(validator, Validator): + return validator + else: + raise AttributeDefinitionError('%s is not a valid validator' % + str(validator)) + + +class Validated(object): + """Base class for other classes that require validation. + + A class which intends to use validated fields should sub-class itself from + this class. Each class should define an 'ATTRIBUTES' class variable which + should be a map from attribute name to its validator. For example: + + class Story(Validated): + ATTRIBUTES = {'title': Type(str), + 'authors': Repeated(Type(str)), + 'isbn': Optional(Type(str)), + 'pages': Type(int), + } + + Attributes that are not listed under ATTRIBUTES work like normal and are + not validated upon assignment. + """ + + ATTRIBUTES = None + + def __init__(self, **attributes): + """Constructor for Validated classes. + + This constructor can optionally assign values to the class via its + keyword arguments. + + Raises: + AttributeDefinitionError when class instance is missing ATTRIBUTE + definition or when ATTRIBUTE is of the wrong type. + """ + if not isinstance(self.ATTRIBUTES, dict): + raise AttributeDefinitionError( + 'The class %s does not define an ATTRIBUTE variable.' + % self.__class__) + + for key in self.ATTRIBUTES.keys(): + object.__setattr__(self, key, self.GetAttribute(key).default) + + self.Set(**attributes) + + @classmethod + def GetAttribute(self, key): + """Safely get the underlying attribute definition as a Validator. + + Args: + key: Name of attribute to get. + + Returns: + Validator associated with key or attribute value wrapped in a + validator. + """ + return AsValidator(self.ATTRIBUTES[key]) + + def Set(self, **attributes): + """Set multiple values on Validated instance. + + This method can only be used to assign validated methods. + + Args: + attributes: Attributes to set on object. + + Raises: + ValidationError when no validated attribute exists on class. + """ + for key, value in attributes.iteritems(): + if key not in self.ATTRIBUTES: + raise ValidationError('Class \'%s\' does not have attribute \'%s\'' + % (self.__class__, key)) + setattr(self, key, value) + + def CheckInitialized(self): + """Checks that all required fields are initialized. + + Since an instance of Validated starts off in an uninitialized state, it + is sometimes necessary to check that it has been fully initialized. + The main problem this solves is how to validate that an instance has + all of its required fields set. By default, Validator classes do not + allow None, but all attributes are initialized to None when instantiated. + + Raises: + Exception relevant to the kind of validation. The type of the exception + is determined by the validator. Typically this will be ValueError or + TypeError. + """ + for key in self.ATTRIBUTES.iterkeys(): + try: + self.GetAttribute(key)(getattr(self, key)) + except MissingAttribute, e: + e.message = "Missing required value '%s'." % key + raise e + + + def __setattr__(self, key, value): + """Set attribute. + + Setting a value on an object of this type will only work for attributes + defined in ATTRIBUTES. To make other assignments possible it is necessary + to override this method in subclasses. + + It is important that assignment is restricted in this way because + this validation is used as validation for parsing. Absent this restriction + it would be possible for method names to be overwritten. + + Args: + key: Name of attribute to set. + value: Attributes new value. + + Raises: + ValidationError when trying to assign to a value that does not exist. + """ + + if key in self.ATTRIBUTES: + value = self.GetAttribute(key)(value, key) + object.__setattr__(self, key, value) + else: + raise ValidationError('Class \'%s\' does not have attribute \'%s\'' + % (self.__class__, key)) + + def __str__(self): + """Formatted view of validated object and nested values.""" + return repr(self) + + def __repr__(self): + """Formatted view of validated object and nested values.""" + values = [(attr, getattr(self, attr)) for attr in self.ATTRIBUTES] + dent = ' ' + value_list = [] + for attr, value in values: + value_list.append('\n%s%s=%s' % (dent, attr, value)) + + return "<%s %s\n%s>" % (self.__class__.__name__, ' '.join(value_list), dent) + + def __eq__(self, other): + """Equality operator. + + Comparison is done by comparing all attribute values to those in the other + instance. Objects which are not of the same type are not equal. + + Args: + other: Other object to compare against. + + Returns: + True if validated objects are equal, else False. + """ + if type(self) != type(other): + return False + for key in self.ATTRIBUTES.iterkeys(): + if getattr(self, key) != getattr(other, key): + return False + return True + + def __ne__(self, other): + """Inequality operator.""" + return not self.__eq__(other) + + def __hash__(self): + """Hash function for using Validated objects in sets and maps. + + Hash is done by hashing all keys and values and xor'ing them together. + + Returns: + Hash of validated object. + """ + result = 0 + for key in self.ATTRIBUTES.iterkeys(): + value = getattr(self, key) + if isinstance(value, list): + value = tuple(value) + result = result ^ hash(key) ^ hash(value) + return result + + @staticmethod + def _ToValue(validator, value): + """Convert any value to simplified collections and basic types. + + Args: + validator: An instance of Validator that corresponds with 'value'. + May also be 'str' or 'int' if those were used instead of a full + Validator. + value: Value to convert to simplified collections. + + Returns: + The value as a dictionary if it is a Validated object. + A list of items converted to simplified collections if value is a list + or a tuple. + Otherwise, just the value. + """ + if isinstance(value, Validated): + return value.ToDict() + elif isinstance(value, (list, tuple)): + return [Validated._ToValue(validator, item) for item in value] + else: + if isinstance(validator, Validator): + return validator.ToValue(value) + return value + + def ToDict(self): + """Convert Validated object to a dictionary. + + Recursively traverses all of its elements and converts everything to + simplified collections. + + Returns: + A dict of all attributes defined in this classes ATTRIBUTES mapped + to its value. This structure is recursive in that Validated objects + that are referenced by this object and in lists are also converted to + dicts. + """ + result = {} + for name, validator in self.ATTRIBUTES.iteritems(): + value = getattr(self, name) + if not(isinstance(validator, Validator) and value == validator.default): + result[name] = Validated._ToValue(validator, value) + return result + + def ToYAML(self): + """Print validated object as simplified YAML. + + Returns: + Object as a simplified YAML string compatible with parsing using the + SafeLoader. + """ + return yaml.dump(self.ToDict(), + default_flow_style=False, + Dumper=yaml.SafeDumper) + + + +class Validator(object): + """Validator base class. + + Though any callable can be used as a validator, this class encapsulates the + case when a specific validator needs to hold a particular state or + configuration. + + To implement Validator sub-class, override the validate method. + + This class is permitted to change the ultimate value that is set to the + attribute if there is a reasonable way to perform the conversion. + """ + + expected_type = object + + def __init__(self, default=None): + """Constructor. + + Args: + default: Default assignment is made during initialization and will + not pass through validation. + """ + self.default = default + + def __call__(self, value, key='???'): + """Main interface to validator is call mechanism.""" + return self.Validate(value, key) + + def Validate(self, value, key='???'): + """Override this method to customize sub-class behavior. + + Args: + value: Value to validate. + key: Name of the field being validated. + + Returns: + Value if value is valid, or a valid representation of value. + """ + return value + + def ToValue(self, value): + """Convert 'value' to a simplified collection or basic type. + + Subclasses of Validator should override this method when the dumped + representation of 'value' is not simply (value) (e.g. a regex). + + Args: + value: An object of the same type that was returned from Validate(). + + Returns: + An instance of a builtin type (e.g. int, str, dict, etc). By default + it returns 'value' unmodified. + """ + return value + + +class Type(Validator): + """Verifies property is of expected type. + + Can optionally convert value if it is not of the expected type. + + It is possible to specify a required field of a specific type in shorthand + by merely providing the type. This method is slightly less efficient than + providing an explicit type but is not significant unless parsing a large + amount of information: + + class Person(Validated): + ATTRIBUTES = {'name': unicode, + 'age': int, + } + + However, in most instances it is best to use the type constants: + + class Person(Validated): + ATTRIBUTES = {'name': TypeUnicode, + 'age': TypeInt, + } + """ + + def __init__(self, expected_type, convert=True, default=None): + """Initialize Type validator. + + Args: + expected_type: Type that attribute should validate against. + convert: Cause conversion if value is not the right type. + Conversion is done by calling the constructor of the type + with the value as its first parameter. + """ + super(Type, self).__init__(default) + self.expected_type = expected_type + self.convert = convert + + def Validate(self, value, key): + """Validate that value is correct type. + + Args: + value: Value to validate. + key: Name of the field being validated. + + Returns: + None if value is None, value if value is of correct type, converted + value if the validator is configured to convert. + + Raises: + ValidationError if value is not of the right type and validator + is not configured to convert. + """ + if not isinstance(value, self.expected_type): + if value is not None and self.convert: + try: + return self.expected_type(value) + except ValueError, e: + raise ValidationError('Type conversion failed for value \'%s\' ' + 'key %s.' % (value, key), e) + except TypeError, e: + raise ValidationError('Expected value of type %s for key %s, but got ' + '\'%s\'.' % (self.expected_type, key, value)) + else: + raise MissingAttribute('Missing value is required.') + else: + return value + + +TYPE_BOOL = Type(bool) +TYPE_INT = Type(int) +TYPE_LONG = Type(long) +TYPE_STR = Type(str) +TYPE_UNICODE = Type(unicode) +TYPE_FLOAT = Type(float) + + +class Options(Validator): + """Limit field based on pre-determined values. + + Options are used to make sure an enumerated set of values are the only + one permitted for assignment. It is possible to define aliases which + map multiple string values to a single original. An example of usage: + + class ZooAnimal(validated.Class): + ATTRIBUTES = { + 'name': str, + 'kind': Options('platypus', # No aliases + ('rhinoceros', ['rhino']), # One alias + ('canine', ('dog', 'puppy')), # Two aliases + ) + """ + + def __init__(self, *options, **kw): + """Initialize options. + + Args: + options: List of allowed values. + """ + if 'default' in kw: + default = kw['default'] + else: + default = None + + alias_map = {} + def AddAlias(alias, original): + """Set new alias on alias_map. + + Raises: + AttributeDefinitionError when option already exists or if alias is + not of type str.. + """ + if not isinstance(alias, str): + raise AttributeDefinitionError( + 'All option values must be of type str.') + elif alias in alias_map: + raise AttributeDefinitionError( + "Option '%s' already defined for options property." % alias) + alias_map[alias] = original + + for option in options: + if isinstance(option, str): + AddAlias(option, option) + + elif isinstance(option, (list, tuple)): + if len(option) != 2: + raise AttributeDefinitionError("Alias is defined as a list of tuple " + "with two items. The first is the " + "original option, while the second " + "is a list or tuple of str aliases.\n" + "\n Example:\n" + " ('original', ('alias1', " + "'alias2'") + original, aliases = option + AddAlias(original, original) + if not isinstance(aliases, (list, tuple)): + raise AttributeDefinitionError('Alias lists must be a list or tuple') + + for alias in aliases: + AddAlias(alias, original) + + else: + raise AttributeDefinitionError("All options must be of type str " + "or of the form (str, [str...]).") + super(Options, self).__init__(default) + self.options = alias_map + + def Validate(self, value, key): + """Validate options. + + Returns: + Original value for provided alias. + + Raises: + ValidationError when value is not one of predefined values. + """ + if value is None: + raise ValidationError('Value for options field must not be None.') + value = str(value) + if value not in self.options: + raise ValidationError('Value \'%s\' for key %s not in %s.' + % (value, key, self.options)) + return self.options[value] + + +class Optional(Validator): + """Definition of optional attributes. + + Optional values are attributes which can be set to None or left + unset. All values in a basic Validated class are set to None + at initialization. Failure to assign to non-optional values + will result in a validation error when calling CheckInitialized. + """ + + def __init__(self, validator, default=None): + """Initializer. + + This constructor will make a few guesses about the value passed in + as the validator: + + - If the validator argument is a type, it automatically creates a Type + validator around it. + + - If the validator argument is a list or tuple, it automatically + creates an Options validator around it. + + Args: + validator: Optional validation condition. + + Raises: + AttributeDefinitionError if validator is not callable. + """ + self.validator = AsValidator(validator) + self.expected_type = self.validator.expected_type + self.default = default + + def Validate(self, value, key): + """Optionally require a value. + + Normal validators do not accept None. This will accept none on + behalf of the contained validator. + + Args: + value: Value to be validated as optional. + key: Name of the field being validated. + + Returns: + None if value is None, else results of contained validation. + """ + if value is None: + return None + return self.validator(value, key) + + def ToValue(self, value): + """Convert 'value' to a simplified collection or basic type.""" + if value is None: + return None + return self.validator.ToValue(value) + + +class Regex(Validator): + """Regular expression validator. + + Regular expression validator always converts value to string. Note that + matches must be exact. Partial matches will not validate. For example: + + class ClassDescr(Validated): + ATTRIBUTES = { 'name': Regex(r'[a-zA-Z_][a-zA-Z_0-9]*'), + 'parent': Type(type), + } + + Alternatively, any attribute that is defined as a string is automatically + interpreted to be of type Regex. It is possible to specify unicode regex + strings as well. This approach is slightly less efficient, but usually + is not significant unless parsing large amounts of data: + + class ClassDescr(Validated): + ATTRIBUTES = { 'name': r'[a-zA-Z_][a-zA-Z_0-9]*', + 'parent': Type(type), + } + + # This will raise a ValidationError exception. + my_class(name='AName with space', parent=AnotherClass) + """ + + def __init__(self, regex, string_type=unicode, default=None): + """Initialized regex validator. + + Args: + regex: Regular expression string to use for comparison. + + Raises: + AttributeDefinitionError if string_type is not a kind of string. + """ + super(Regex, self).__init__(default) + if (not issubclass(string_type, basestring) or + string_type is basestring): + raise AttributeDefinitionError( + 'Regex fields must be a string type not %s.' % str(string_type)) + if isinstance(regex, basestring): + self.re = re.compile('^%s$' % regex) + else: + raise AttributeDefinitionError( + 'Regular expression must be string. Found %s.' % str(regex)) + + self.expected_type = string_type + + def Validate(self, value, key): + """Does validation of a string against a regular expression. + + Args: + value: String to match against regular expression. + key: Name of the field being validated. + + Raises: + ValidationError when value does not match regular expression or + when value does not match provided string type. + """ + if issubclass(self.expected_type, str): + cast_value = TYPE_STR(value) + else: + cast_value = TYPE_UNICODE(value) + + if self.re.match(cast_value) is None: + raise ValidationError('Value \'%s\' for key %s does not match expression ' + '\'%s\'' % (value, key, self.re.pattern)) + return cast_value + + +class _RegexStrValue(object): + """Simulates the regex object to support recomplation when necessary. + + Used by the RegexStr class to dynamically build and recompile regular + expression attributes of a validated object. This object replaces the normal + object returned from re.compile which is immutable. + + When the value of this object is a string, that string is simply used as the + regular expression when recompilation is needed. If the state of this object + is a list of strings, the strings are joined in to a single 'or' expression. + """ + + def __init__(self, attribute, value, key): + """Initialize recompilable regex value. + + Args: + attribute: Attribute validator associated with this regex value. + value: Initial underlying python value for regex string. Either a single + regex string or a list of regex strings. + key: Name of the field. + """ + self.__attribute = attribute + self.__value = value + self.__regex = None + self.__key = key + + def __AsString(self, value): + """Convert a value to appropriate string. + + Returns: + String version of value with all carriage returns and line feeds removed. + """ + if issubclass(self.__attribute.expected_type, str): + cast_value = TYPE_STR(value) + else: + cast_value = TYPE_UNICODE(value) + + cast_value = cast_value.replace('\n', '') + cast_value = cast_value.replace('\r', '') + return cast_value + + def __BuildRegex(self): + """Build regex string from state. + + Returns: + String version of regular expression. Sequence objects are constructed + as larger regular expression where each regex in the list is joined with + all the others as single 'or' expression. + """ + if isinstance(self.__value, list): + value_list = self.__value + sequence = True + else: + value_list = [self.__value] + sequence = False + + regex_list = [] + for item in value_list: + regex_list.append(self.__AsString(item)) + + if sequence: + return '|'.join('(?:%s)' % item for item in regex_list) + else: + return regex_list[0] + + def __Compile(self): + """Build regular expression object from state. + + Returns: + Compiled regular expression based on internal value. + """ + regex = self.__BuildRegex() + try: + return re.compile(regex) + except re.error, e: + raise ValidationError('Value \'%s\' for key %s does not compile: %s' % + (regex, self.__key, e), e) + + @property + def regex(self): + """Compiled regular expression as described by underlying value.""" + return self.__Compile() + + def match(self, value): + """Match against internal regular expression. + + Returns: + Regular expression object built from underlying value. + """ + return re.match(self.__BuildRegex(), value) + + def Validate(self): + """Ensure that regex string compiles.""" + self.__Compile() + + def __str__(self): + """Regular expression string as described by underlying value.""" + return self.__BuildRegex() + + def __eq__(self, other): + """Comparison against other regular expression string values.""" + if isinstance(other, _RegexStrValue): + return self.__BuildRegex() == other.__BuildRegex() + return str(self) == other + + def __ne__(self, other): + """Inequality operator for regular expression string value.""" + return not self.__eq__(other) + + +class RegexStr(Validator): + """Validates that a string can compile as a regex without errors. + + Use this validator when the value of a field should be a regex. That + means that the value must be a string that can be compiled by re.compile(). + The attribute will then be a compiled re object. + """ + + def __init__(self, string_type=unicode, default=None): + """Initialized regex validator. + + Raises: + AttributeDefinitionError if string_type is not a kind of string. + """ + if default is not None: + default = _RegexStrValue(self, default, None) + re.compile(str(default)) + super(RegexStr, self).__init__(default) + if (not issubclass(string_type, basestring) or + string_type is basestring): + raise AttributeDefinitionError( + 'RegexStr fields must be a string type not %s.' % str(string_type)) + + self.expected_type = string_type + + def Validate(self, value, key): + """Validates that the string compiles as a regular expression. + + Because the regular expression might have been expressed as a multiline + string, this function also strips newlines out of value. + + Args: + value: String to compile as a regular expression. + key: Name of the field being validated. + + Raises: + ValueError when value does not compile as a regular expression. TypeError + when value does not match provided string type. + """ + if isinstance(value, _RegexStrValue): + return value + value = _RegexStrValue(self, value, key) + value.Validate() + return value + + def ToValue(self, value): + """Returns the RE pattern for this validator.""" + return str(value) + + +class Range(Validator): + """Validates that numbers fall within the correct range. + + In theory this class can be emulated using Options, however error + messages generated from that class will not be very intelligible. + This class essentially does the same thing, but knows the intended + integer range. + + Also, this range class supports floats and other types that implement + ordinality. + + The range is inclusive, meaning 3 is considered in the range + in Range(1,3). + """ + + def __init__(self, minimum, maximum, range_type=int, default=None): + """Initializer for range. + + Args: + minimum: Minimum for attribute. + maximum: Maximum for attribute. + range_type: Type of field. Defaults to int. + """ + super(Range, self).__init__(default) + if not isinstance(minimum, range_type): + raise AttributeDefinitionError( + 'Minimum value must be of type %s, instead it is %s (%s).' % + (str(range_type), str(type(minimum)), str(minimum))) + if not isinstance(maximum, range_type): + raise AttributeDefinitionError( + 'Maximum value must be of type %s, instead it is %s (%s).' % + (str(range_type), str(type(maximum)), str(maximum))) + + self.minimum = minimum + self.maximum = maximum + self.expected_type = range_type + self._type_validator = Type(range_type) + + def Validate(self, value, key): + """Validate that value is within range. + + Validates against range-type then checks the range. + + Args: + value: Value to validate. + key: Name of the field being validated. + + Raises: + ValidationError when value is out of range. ValidationError when value + is notd of the same range type. + """ + cast_value = self._type_validator.Validate(value, key) + if cast_value < self.minimum or cast_value > self.maximum: + raise ValidationError('Value \'%s\' for %s is out of range %s - %s' + % (str(value), + key, + str(self.minimum), + str(self.maximum))) + return cast_value + + +class Repeated(Validator): + """Repeated field validator. + + Indicates that attribute is expected to be a repeated value, ie, + a sequence. This adds additional validation over just Type(list) + in that it retains information about what can be stored in the list by + use of its constructor field. + """ + + def __init__(self, constructor, default=None): + """Initializer for repeated field. + + Args: + constructor: Type used for verifying elements of sequence attribute. + """ + super(Repeated, self).__init__(default) + self.constructor = constructor + self.expected_type = list + + def Validate(self, value, key): + """Do validation of sequence. + + Value must be a list and all elements must be of type 'constructor'. + + Args: + value: Value to validate. + key: Name of the field being validated. + + Raises: + ValidationError if value is None, not a list or one of its elements is the + wrong type. + """ + if not isinstance(value, list): + raise ValidationError('Repeated fields for %s must be sequence, ' + 'but found \'%s\'.' % (key, value)) + + for item in value: + if isinstance(self.constructor, Validator): + item = self.constructor.Validate(item, key) + elif not isinstance(item, self.constructor): + raise ValidationError('Repeated items for %s must be %s, but found ' + '\'%s\'.' % + (key, str(self.constructor), str(item))) + + return value diff --git a/google_appengine/google/appengine/api/validation.pyc b/google_appengine/google/appengine/api/validation.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de6c47097f7480965e90541a7d1eddb2c6e7f91a GIT binary patch literal 36125 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/xmpp/__init__.py b/google_appengine/google/appengine/api/xmpp/__init__.py new file mode 100755 index 0000000..8cc477a --- /dev/null +++ b/google_appengine/google/appengine/api/xmpp/__init__.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""XMPP API. + +This module allows AppEngine apps to interact with a bot representing that app +on the Google Talk network. + +Functions defined in this module: + get_presence: Gets the presence for a JID. + send_message: Sends a chat message to any number of JIDs. + send_invite: Sends an invitation to chat to a JID. + +Classes defined in this module: + Message: A class to encapsulate received messages. +""" + + + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api.xmpp import xmpp_service_pb +from google.appengine.runtime import apiproxy_errors + + +NO_ERROR = xmpp_service_pb.XmppMessageResponse.NO_ERROR +INVALID_JID = xmpp_service_pb.XmppMessageResponse.INVALID_JID +OTHER_ERROR = xmpp_service_pb.XmppMessageResponse.OTHER_ERROR + + +MESSAGE_TYPE_NONE = "" +MESSAGE_TYPE_CHAT = "chat" +MESSAGE_TYPE_ERROR = "error" +MESSAGE_TYPE_GROUPCHAT = "groupchat" +MESSAGE_TYPE_HEADLINE = "headline" +MESSAGE_TYPE_NORMAL = "normal" + +_VALID_MESSAGE_TYPES = frozenset([MESSAGE_TYPE_NONE, MESSAGE_TYPE_CHAT, + MESSAGE_TYPE_ERROR, MESSAGE_TYPE_GROUPCHAT, + MESSAGE_TYPE_HEADLINE, MESSAGE_TYPE_NORMAL]) + + +class Error(Exception): + """Base error class for this module.""" + + +class InvalidJidError(Error): + """Error that indicates a request for an invalid JID.""" + + +class InvalidTypeError(Error): + """Error that indicates a send message request has an invalid type.""" + + +class InvalidXmlError(Error): + """Error that indicates a send message request has invalid XML.""" + + +class NoBodyError(Error): + """Error that indicates a send message request has no body.""" + + +class InvalidMessageError(Error): + """Error that indicates a received message was invalid or incomplete.""" + + +def get_presence(jid, from_jid=None): + """Gets the presence for a JID. + + Args: + jid: The JID of the contact whose presence is requested. + from_jid: The optional custom JID to use for sending. Currently, the default + is @appspot.com. This is supported as a value. Custom JIDs can be + of the form @.appspotchat.com. + + Returns: + bool, Whether the user is online. + + Raises: + InvalidJidError if any of the JIDs passed are invalid. + Error if an unspecified error happens processing the request. + """ + if not jid: + raise InvalidJidError() + + request = xmpp_service_pb.PresenceRequest() + response = xmpp_service_pb.PresenceResponse() + + request.set_jid(_to_str(jid)) + if from_jid: + request.set_from_jid(_to_str(from_jid)) + + try: + apiproxy_stub_map.MakeSyncCall("xmpp", + "GetPresence", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + xmpp_service_pb.XmppServiceError.INVALID_JID): + raise InvalidJidError() + else: + raise Error() + + return bool(response.is_available()) + + +def send_invite(jid, from_jid=None): + """Sends an invitation to chat to a JID. + + Args: + jid: The JID of the contact to invite. + from_jid: The optional custom JID to use for sending. Currently, the default + is @appspot.com. This is supported as a value. Custom JIDs can be + of the form @.appspotchat.com. + + Raises: + InvalidJidError if the JID passed is invalid. + Error if an unspecified error happens processing the request. + """ + if not jid: + raise InvalidJidError() + + request = xmpp_service_pb.XmppInviteRequest() + response = xmpp_service_pb.XmppInviteResponse() + + request.set_jid(_to_str(jid)) + if from_jid: + request.set_from_jid(_to_str(from_jid)) + + try: + apiproxy_stub_map.MakeSyncCall("xmpp", + "SendInvite", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + xmpp_service_pb.XmppServiceError.INVALID_JID): + raise InvalidJidError() + else: + raise Error() + + return + + +def send_message(jids, body, from_jid=None, message_type=MESSAGE_TYPE_CHAT, + raw_xml=False): + """Sends a chat message to a list of JIDs. + + Args: + jids: A list of JIDs to send the message to, or a single JID to send the + message to. + from_jid: The optional custom JID to use for sending. Currently, the default + is @appspot.com. This is supported as a value. Custom JIDs can be + of the form @.appspotchat.com. + body: The body of the message. + message_type: Optional type of the message. Should be one of the types + specified in RFC 3921, section 2.1.1. An empty string will result in a + message stanza without a type attribute. For convenience, all of the + valid types are in the MESSAGE_TYPE_* constants in this file. The + default is MESSAGE_TYPE_CHAT. Anything else will throw an exception. + raw_xml: Optionally specifies that the body should be interpreted as XML. If + this is false, the contents of the body will be escaped and placed inside + of a body element inside of the message. If this is true, the contents + will be made children of the message. + + Returns: + list, A list of statuses, one for each JID, corresponding to the result of + sending the message to that JID. Or, if a single JID was passed in, + returns the status directly. + + Raises: + InvalidJidError if there is no valid JID in the list. + InvalidTypeError if the type argument is invalid. + InvalidXmlError if the body is malformed XML and raw_xml is True. + NoBodyError if there is no body. + Error if another error occurs processing the request. + """ + request = xmpp_service_pb.XmppMessageRequest() + response = xmpp_service_pb.XmppMessageResponse() + + if not body: + raise NoBodyError() + + if not jids: + raise InvalidJidError() + + if not message_type in _VALID_MESSAGE_TYPES: + raise InvalidTypeError() + + single_jid = False + if isinstance(jids, basestring): + single_jid = True + jids = [jids] + + for jid in jids: + if not jid: + raise InvalidJidError() + request.add_jid(_to_str(jid)) + + request.set_body(_to_str(body)) + request.set_type(_to_str(message_type)) + request.set_raw_xml(raw_xml) + if from_jid: + request.set_from_jid(_to_str(from_jid)) + + try: + apiproxy_stub_map.MakeSyncCall("xmpp", + "SendMessage", + request, + response) + except apiproxy_errors.ApplicationError, e: + if (e.application_error == + xmpp_service_pb.XmppServiceError.INVALID_JID): + raise InvalidJidError() + elif (e.application_error == + xmpp_service_pb.XmppServiceError.INVALID_TYPE): + raise InvalidTypeError() + elif (e.application_error == + xmpp_service_pb.XmppServiceError.INVALID_XML): + raise InvalidXmlError() + elif (e.application_error == + xmpp_service_pb.XmppServiceError.NO_BODY): + raise NoBodyError() + raise Error() + + if single_jid: + return response.status_list()[0] + return response.status_list() + + +class Message(object): + """Encapsulates an XMPP message received by the application.""" + + def __init__(self, vars): + """Constructs a new XMPP Message from an HTTP request. + + Args: + vars: A dict-like object to extract message arguments from. + """ + try: + self.__sender = vars["from"] + self.__to = vars["to"] + self.__body = vars["body"] + except KeyError, e: + raise InvalidMessageError(e[0]) + self.__command = None + self.__arg = None + + @property + def sender(self): + return self.__sender + + @property + def to(self): + return self.__to + + @property + def body(self): + return self.__body + + def __parse_command(self): + if self.__arg != None: + return + + body = self.__body + if body.startswith('\\'): + body = '/' + body[1:] + + self.__arg = '' + if body.startswith('/'): + parts = body.split(' ', 1) + self.__command = parts[0][1:] + if len(parts) > 1: + self.__arg = parts[1].strip() + else: + self.__arg = self.__body.strip() + + @property + def command(self): + self.__parse_command() + return self.__command + + @property + def arg(self): + self.__parse_command() + return self.__arg + + def reply(self, body, message_type=MESSAGE_TYPE_CHAT, raw_xml=False, + send_message=send_message): + """Convenience function to reply to a message. + + Args: + body: str: The body of the message + message_type, raw_xml: As per send_message. + send_message: Used for testing. + + Returns: + A status code as per send_message. + + Raises: + See send_message. + """ + return send_message([self.sender], body, from_jid=self.to, + message_type=message_type, raw_xml=raw_xml) + + +def _to_str(value): + """Helper function to make sure unicode values converted to utf-8 + + Args: + value: str or unicode to convert to utf-8. + + Returns: + UTF-8 encoded str of value, otherwise value unchanged. + """ + if isinstance(value, unicode): + return value.encode('utf-8') + return value diff --git a/google_appengine/google/appengine/api/xmpp/__init__.pyc b/google_appengine/google/appengine/api/xmpp/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69850c5f9b668b0eb387dbe871d208e5ebfeb3b5 GIT binary patch literal 11304 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 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class PresenceRequest(ProtocolBuffer.ProtocolMessage): + has_jid_ = 0 + jid_ = "" + has_from_jid_ = 0 + from_jid_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def jid(self): return self.jid_ + + def set_jid(self, x): + self.has_jid_ = 1 + self.jid_ = x + + def clear_jid(self): + if self.has_jid_: + self.has_jid_ = 0 + self.jid_ = "" + + def has_jid(self): return self.has_jid_ + + def from_jid(self): return self.from_jid_ + + def set_from_jid(self, x): + self.has_from_jid_ = 1 + self.from_jid_ = x + + def clear_from_jid(self): + if self.has_from_jid_: + self.has_from_jid_ = 0 + self.from_jid_ = "" + + def has_from_jid(self): return self.has_from_jid_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_jid()): self.set_jid(x.jid()) + if (x.has_from_jid()): self.set_from_jid(x.from_jid()) + + def Equals(self, x): + if x is self: return 1 + if self.has_jid_ != x.has_jid_: return 0 + if self.has_jid_ and self.jid_ != x.jid_: return 0 + if self.has_from_jid_ != x.has_from_jid_: return 0 + if self.has_from_jid_ and self.from_jid_ != x.from_jid_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_jid_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: jid not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.jid_)) + if (self.has_from_jid_): n += 1 + self.lengthString(len(self.from_jid_)) + return n + 1 + + def Clear(self): + self.clear_jid() + self.clear_from_jid() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.jid_) + if (self.has_from_jid_): + out.putVarInt32(18) + out.putPrefixedString(self.from_jid_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_jid(d.getPrefixedString()) + continue + if tt == 18: + self.set_from_jid(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_jid_: res+=prefix+("jid: %s\n" % self.DebugFormatString(self.jid_)) + if self.has_from_jid_: res+=prefix+("from_jid: %s\n" % self.DebugFormatString(self.from_jid_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kjid = 1 + kfrom_jid = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "jid", + 2: "from_jid", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class PresenceResponse(ProtocolBuffer.ProtocolMessage): + + NORMAL = 0 + AWAY = 1 + DO_NOT_DISTURB = 2 + CHAT = 3 + EXTENDED_AWAY = 4 + + _SHOW_NAMES = { + 0: "NORMAL", + 1: "AWAY", + 2: "DO_NOT_DISTURB", + 3: "CHAT", + 4: "EXTENDED_AWAY", + } + + def SHOW_Name(cls, x): return cls._SHOW_NAMES.get(x, "") + SHOW_Name = classmethod(SHOW_Name) + + has_is_available_ = 0 + is_available_ = 0 + has_presence_ = 0 + presence_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def is_available(self): return self.is_available_ + + def set_is_available(self, x): + self.has_is_available_ = 1 + self.is_available_ = x + + def clear_is_available(self): + if self.has_is_available_: + self.has_is_available_ = 0 + self.is_available_ = 0 + + def has_is_available(self): return self.has_is_available_ + + def presence(self): return self.presence_ + + def set_presence(self, x): + self.has_presence_ = 1 + self.presence_ = x + + def clear_presence(self): + if self.has_presence_: + self.has_presence_ = 0 + self.presence_ = 0 + + def has_presence(self): return self.has_presence_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_is_available()): self.set_is_available(x.is_available()) + if (x.has_presence()): self.set_presence(x.presence()) + + def Equals(self, x): + if x is self: return 1 + if self.has_is_available_ != x.has_is_available_: return 0 + if self.has_is_available_ and self.is_available_ != x.is_available_: return 0 + if self.has_presence_ != x.has_presence_: return 0 + if self.has_presence_ and self.presence_ != x.presence_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_is_available_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: is_available not set.') + return initialized + + def ByteSize(self): + n = 0 + if (self.has_presence_): n += 1 + self.lengthVarInt64(self.presence_) + return n + 2 + + def Clear(self): + self.clear_is_available() + self.clear_presence() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putBoolean(self.is_available_) + if (self.has_presence_): + out.putVarInt32(16) + out.putVarInt32(self.presence_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_is_available(d.getBoolean()) + continue + if tt == 16: + self.set_presence(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_is_available_: res+=prefix+("is_available: %s\n" % self.DebugFormatBool(self.is_available_)) + if self.has_presence_: res+=prefix+("presence: %s\n" % self.DebugFormatInt32(self.presence_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kis_available = 1 + kpresence = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "is_available", + 2: "presence", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppMessageRequest(ProtocolBuffer.ProtocolMessage): + has_body_ = 0 + body_ = "" + has_raw_xml_ = 0 + raw_xml_ = 0 + has_type_ = 0 + type_ = "chat" + has_from_jid_ = 0 + from_jid_ = "" + + def __init__(self, contents=None): + self.jid_ = [] + if contents is not None: self.MergeFromString(contents) + + def jid_size(self): return len(self.jid_) + def jid_list(self): return self.jid_ + + def jid(self, i): + return self.jid_[i] + + def set_jid(self, i, x): + self.jid_[i] = x + + def add_jid(self, x): + self.jid_.append(x) + + def clear_jid(self): + self.jid_ = [] + + def body(self): return self.body_ + + def set_body(self, x): + self.has_body_ = 1 + self.body_ = x + + def clear_body(self): + if self.has_body_: + self.has_body_ = 0 + self.body_ = "" + + def has_body(self): return self.has_body_ + + def raw_xml(self): return self.raw_xml_ + + def set_raw_xml(self, x): + self.has_raw_xml_ = 1 + self.raw_xml_ = x + + def clear_raw_xml(self): + if self.has_raw_xml_: + self.has_raw_xml_ = 0 + self.raw_xml_ = 0 + + def has_raw_xml(self): return self.has_raw_xml_ + + def type(self): return self.type_ + + def set_type(self, x): + self.has_type_ = 1 + self.type_ = x + + def clear_type(self): + if self.has_type_: + self.has_type_ = 0 + self.type_ = "chat" + + def has_type(self): return self.has_type_ + + def from_jid(self): return self.from_jid_ + + def set_from_jid(self, x): + self.has_from_jid_ = 1 + self.from_jid_ = x + + def clear_from_jid(self): + if self.has_from_jid_: + self.has_from_jid_ = 0 + self.from_jid_ = "" + + def has_from_jid(self): return self.has_from_jid_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.jid_size()): self.add_jid(x.jid(i)) + if (x.has_body()): self.set_body(x.body()) + if (x.has_raw_xml()): self.set_raw_xml(x.raw_xml()) + if (x.has_type()): self.set_type(x.type()) + if (x.has_from_jid()): self.set_from_jid(x.from_jid()) + + def Equals(self, x): + if x is self: return 1 + if len(self.jid_) != len(x.jid_): return 0 + for e1, e2 in zip(self.jid_, x.jid_): + if e1 != e2: return 0 + if self.has_body_ != x.has_body_: return 0 + if self.has_body_ and self.body_ != x.body_: return 0 + if self.has_raw_xml_ != x.has_raw_xml_: return 0 + if self.has_raw_xml_ and self.raw_xml_ != x.raw_xml_: return 0 + if self.has_type_ != x.has_type_: return 0 + if self.has_type_ and self.type_ != x.type_: return 0 + if self.has_from_jid_ != x.has_from_jid_: return 0 + if self.has_from_jid_ and self.from_jid_ != x.from_jid_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_body_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: body not set.') + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.jid_) + for i in xrange(len(self.jid_)): n += self.lengthString(len(self.jid_[i])) + n += self.lengthString(len(self.body_)) + if (self.has_raw_xml_): n += 2 + if (self.has_type_): n += 1 + self.lengthString(len(self.type_)) + if (self.has_from_jid_): n += 1 + self.lengthString(len(self.from_jid_)) + return n + 1 + + def Clear(self): + self.clear_jid() + self.clear_body() + self.clear_raw_xml() + self.clear_type() + self.clear_from_jid() + + def OutputUnchecked(self, out): + for i in xrange(len(self.jid_)): + out.putVarInt32(10) + out.putPrefixedString(self.jid_[i]) + out.putVarInt32(18) + out.putPrefixedString(self.body_) + if (self.has_raw_xml_): + out.putVarInt32(24) + out.putBoolean(self.raw_xml_) + if (self.has_type_): + out.putVarInt32(34) + out.putPrefixedString(self.type_) + if (self.has_from_jid_): + out.putVarInt32(42) + out.putPrefixedString(self.from_jid_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_jid(d.getPrefixedString()) + continue + if tt == 18: + self.set_body(d.getPrefixedString()) + continue + if tt == 24: + self.set_raw_xml(d.getBoolean()) + continue + if tt == 34: + self.set_type(d.getPrefixedString()) + continue + if tt == 42: + self.set_from_jid(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.jid_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("jid%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_body_: res+=prefix+("body: %s\n" % self.DebugFormatString(self.body_)) + if self.has_raw_xml_: res+=prefix+("raw_xml: %s\n" % self.DebugFormatBool(self.raw_xml_)) + if self.has_type_: res+=prefix+("type: %s\n" % self.DebugFormatString(self.type_)) + if self.has_from_jid_: res+=prefix+("from_jid: %s\n" % self.DebugFormatString(self.from_jid_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kjid = 1 + kbody = 2 + kraw_xml = 3 + ktype = 4 + kfrom_jid = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "jid", + 2: "body", + 3: "raw_xml", + 4: "type", + 5: "from_jid", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppMessageResponse(ProtocolBuffer.ProtocolMessage): + + NO_ERROR = 0 + INVALID_JID = 1 + OTHER_ERROR = 2 + + _XmppMessageStatus_NAMES = { + 0: "NO_ERROR", + 1: "INVALID_JID", + 2: "OTHER_ERROR", + } + + def XmppMessageStatus_Name(cls, x): return cls._XmppMessageStatus_NAMES.get(x, "") + XmppMessageStatus_Name = classmethod(XmppMessageStatus_Name) + + + def __init__(self, contents=None): + self.status_ = [] + if contents is not None: self.MergeFromString(contents) + + def status_size(self): return len(self.status_) + def status_list(self): return self.status_ + + def status(self, i): + return self.status_[i] + + def set_status(self, i, x): + self.status_[i] = x + + def add_status(self, x): + self.status_.append(x) + + def clear_status(self): + self.status_ = [] + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.status_size()): self.add_status(x.status(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.status_) != len(x.status_): return 0 + for e1, e2 in zip(self.status_, x.status_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.status_) + for i in xrange(len(self.status_)): n += self.lengthVarInt64(self.status_[i]) + return n + 0 + + def Clear(self): + self.clear_status() + + def OutputUnchecked(self, out): + for i in xrange(len(self.status_)): + out.putVarInt32(8) + out.putVarInt32(self.status_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.add_status(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.status_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("status%s: %s\n" % (elm, self.DebugFormatInt32(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kstatus = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "status", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppSendPresenceRequest(ProtocolBuffer.ProtocolMessage): + has_jid_ = 0 + jid_ = "" + has_type_ = 0 + type_ = "" + has_show_ = 0 + show_ = "" + has_status_ = 0 + status_ = "" + has_from_jid_ = 0 + from_jid_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def jid(self): return self.jid_ + + def set_jid(self, x): + self.has_jid_ = 1 + self.jid_ = x + + def clear_jid(self): + if self.has_jid_: + self.has_jid_ = 0 + self.jid_ = "" + + def has_jid(self): return self.has_jid_ + + def type(self): return self.type_ + + def set_type(self, x): + self.has_type_ = 1 + self.type_ = x + + def clear_type(self): + if self.has_type_: + self.has_type_ = 0 + self.type_ = "" + + def has_type(self): return self.has_type_ + + def show(self): return self.show_ + + def set_show(self, x): + self.has_show_ = 1 + self.show_ = x + + def clear_show(self): + if self.has_show_: + self.has_show_ = 0 + self.show_ = "" + + def has_show(self): return self.has_show_ + + def status(self): return self.status_ + + def set_status(self, x): + self.has_status_ = 1 + self.status_ = x + + def clear_status(self): + if self.has_status_: + self.has_status_ = 0 + self.status_ = "" + + def has_status(self): return self.has_status_ + + def from_jid(self): return self.from_jid_ + + def set_from_jid(self, x): + self.has_from_jid_ = 1 + self.from_jid_ = x + + def clear_from_jid(self): + if self.has_from_jid_: + self.has_from_jid_ = 0 + self.from_jid_ = "" + + def has_from_jid(self): return self.has_from_jid_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_jid()): self.set_jid(x.jid()) + if (x.has_type()): self.set_type(x.type()) + if (x.has_show()): self.set_show(x.show()) + if (x.has_status()): self.set_status(x.status()) + if (x.has_from_jid()): self.set_from_jid(x.from_jid()) + + def Equals(self, x): + if x is self: return 1 + if self.has_jid_ != x.has_jid_: return 0 + if self.has_jid_ and self.jid_ != x.jid_: return 0 + if self.has_type_ != x.has_type_: return 0 + if self.has_type_ and self.type_ != x.type_: return 0 + if self.has_show_ != x.has_show_: return 0 + if self.has_show_ and self.show_ != x.show_: return 0 + if self.has_status_ != x.has_status_: return 0 + if self.has_status_ and self.status_ != x.status_: return 0 + if self.has_from_jid_ != x.has_from_jid_: return 0 + if self.has_from_jid_ and self.from_jid_ != x.from_jid_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_jid_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: jid not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.jid_)) + if (self.has_type_): n += 1 + self.lengthString(len(self.type_)) + if (self.has_show_): n += 1 + self.lengthString(len(self.show_)) + if (self.has_status_): n += 1 + self.lengthString(len(self.status_)) + if (self.has_from_jid_): n += 1 + self.lengthString(len(self.from_jid_)) + return n + 1 + + def Clear(self): + self.clear_jid() + self.clear_type() + self.clear_show() + self.clear_status() + self.clear_from_jid() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.jid_) + if (self.has_type_): + out.putVarInt32(18) + out.putPrefixedString(self.type_) + if (self.has_show_): + out.putVarInt32(26) + out.putPrefixedString(self.show_) + if (self.has_status_): + out.putVarInt32(34) + out.putPrefixedString(self.status_) + if (self.has_from_jid_): + out.putVarInt32(42) + out.putPrefixedString(self.from_jid_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_jid(d.getPrefixedString()) + continue + if tt == 18: + self.set_type(d.getPrefixedString()) + continue + if tt == 26: + self.set_show(d.getPrefixedString()) + continue + if tt == 34: + self.set_status(d.getPrefixedString()) + continue + if tt == 42: + self.set_from_jid(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_jid_: res+=prefix+("jid: %s\n" % self.DebugFormatString(self.jid_)) + if self.has_type_: res+=prefix+("type: %s\n" % self.DebugFormatString(self.type_)) + if self.has_show_: res+=prefix+("show: %s\n" % self.DebugFormatString(self.show_)) + if self.has_status_: res+=prefix+("status: %s\n" % self.DebugFormatString(self.status_)) + if self.has_from_jid_: res+=prefix+("from_jid: %s\n" % self.DebugFormatString(self.from_jid_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kjid = 1 + ktype = 2 + kshow = 3 + kstatus = 4 + kfrom_jid = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "jid", + 2: "type", + 3: "show", + 4: "status", + 5: "from_jid", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppSendPresenceResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppInviteRequest(ProtocolBuffer.ProtocolMessage): + has_jid_ = 0 + jid_ = "" + has_from_jid_ = 0 + from_jid_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def jid(self): return self.jid_ + + def set_jid(self, x): + self.has_jid_ = 1 + self.jid_ = x + + def clear_jid(self): + if self.has_jid_: + self.has_jid_ = 0 + self.jid_ = "" + + def has_jid(self): return self.has_jid_ + + def from_jid(self): return self.from_jid_ + + def set_from_jid(self, x): + self.has_from_jid_ = 1 + self.from_jid_ = x + + def clear_from_jid(self): + if self.has_from_jid_: + self.has_from_jid_ = 0 + self.from_jid_ = "" + + def has_from_jid(self): return self.has_from_jid_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_jid()): self.set_jid(x.jid()) + if (x.has_from_jid()): self.set_from_jid(x.from_jid()) + + def Equals(self, x): + if x is self: return 1 + if self.has_jid_ != x.has_jid_: return 0 + if self.has_jid_ and self.jid_ != x.jid_: return 0 + if self.has_from_jid_ != x.has_from_jid_: return 0 + if self.has_from_jid_ and self.from_jid_ != x.from_jid_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_jid_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: jid not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.jid_)) + if (self.has_from_jid_): n += 1 + self.lengthString(len(self.from_jid_)) + return n + 1 + + def Clear(self): + self.clear_jid() + self.clear_from_jid() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.jid_) + if (self.has_from_jid_): + out.putVarInt32(18) + out.putPrefixedString(self.from_jid_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_jid(d.getPrefixedString()) + continue + if tt == 18: + self.set_from_jid(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_jid_: res+=prefix+("jid: %s\n" % self.DebugFormatString(self.jid_)) + if self.has_from_jid_: res+=prefix+("from_jid: %s\n" % self.DebugFormatString(self.from_jid_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kjid = 1 + kfrom_jid = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "jid", + 2: "from_jid", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class XmppInviteResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['XmppServiceError','PresenceRequest','PresenceResponse','XmppMessageRequest','XmppMessageResponse','XmppSendPresenceRequest','XmppSendPresenceResponse','XmppInviteRequest','XmppInviteResponse'] diff --git a/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.pyc b/google_appengine/google/appengine/api/xmpp/xmpp_service_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..917d286ed66497d9ba554b01254069304eeff9ef GIT binary patch literal 50096 zcwX&YYiu0Xbw0DZ+!uFAN)$;^k|mOQn3iqHkC<}P#I+>S788k5hqNs-an>u&h+K2I zOYIJA%Au0jbW+!C`be6lY5H&yr*(rq5fll~0u71)>5l|Kfb>VxA}CU{1=>GFiWY5A zpuOL@b7$`E&Qc^r&eF0i@8vx1%-nPCIrpCP-E(HX_v38(n+L4(Gvxl)C4P^JU-emo z$P)h>G*47CXxbnH`w^Ot&~${{?I=yh$P#56ABoX?oTl7!ahgu3qe<^*l8?5LZBQXa zR}He_^a{}`(bLmuvJ%>fq<5m7tdw>l?VZSw)vle$cqg)CWwjF>-iZ#fI<*sB-ic1K zc4;SedndZc>ef!|@lNa_t4BN0>z&vQ0tG3e(V}^xAr{|U+(uk5o1*D%uwrt!PjI^M zf8xJk!EJInL~*H7cFHs5;+dtZSMAC~RdAmc^2-(5S*nz(@4at8>|CkjOEYse_Dbb@ zXY$2jp;U0v1C9SJm6ocuRmv}j%8Qjk$r&r!3lmEVSM16?mVISuHdl2jRotbzXwMY# zMcEJ(#~sMR<%Pw?NxSlVVa6V-RLT`kP-5bD27+=zRJIB7Avzd>A!H>A@_fOF(R3Uf zLB|WBNYY$_xkEG}`dC~r0!Br|pENER0ncLMPX-r_fPHZqnNGRAD5S&|eV(RO1KZXC zaQ79loppN?UA&7oI{EBwKFhs`gS*@=L@)L5Mz`B{(|gG3BWo{N{q%|;pocHt$Cvk# z-Ai+V$o;4=B7Uo)MxT4s-b;mkD(q9|-2MHuDmZWecb&e2%Y$T24&Q-p2agL86j2o5 zXQ2&697O`%uuBwAOiW%HJ3Ic^_}FM}%rq~WPP;fdKJnz~^W&qr_l=J_s5)^mcjn^g z(>3QVUpVh%wEd~4FOAh4m^^p!DSpP-<+EdBqv8P780fTBNH8i*I1mWlWSPmaq?|2Vc5dSIg|SHocbT;vSpz#W#VX6#@-SbCqB<#x zlXK+-`($aRk}sbeEx%AImh)Ei zxy36d7O%5XX$=ypa6cZ0XL@8t6q!RHlO_4N6qR?8v7!hT!e*4@7&ysFG9k%9zfF`& zw$mv#F4&b>`>{%SVbT%GHtW}@+Qq9*5|1pG9J}OHLm(WF%jGaKM325)ORP6Qovw<~=@OV)Q zfe5J0nd^={DF_&AXutZMRcAC6;kFs7!67si)5Y1Mov(~jqZ+K)N}sI|pYoCuLTfQV zUR-h(mz*bHz0Azp*7>@2M@f?oQxwbZhiDYotP%q9T#U-6#Gq_oP!@-zPT)j@%2_dN zMKGI+kqaX=YQ=CKL-(~P8BZ}6GzkO$n2Gu4^MxWSjF_gL6#OhX4?ckBlhaT2Hrc2> zBW4mF{a9G4^M%FHypz{75uCOh9{t1MELt&Dxz3~FMLa0OER9mPkr9JR)-W+NFwHY) zBqX9?c!|VW$wy4bCLzAVuvxY!QjbZjLYSoBwaz#1axOY z(e?=1Ejm?@Ea-E1kP&vKkc>WIy^KE7Pd8#9dxcH06)7zd+I?+5eIyFGndDBD;v&af z?6b@`ON&LDnX)X*@mbph)1^q^V0AHHsmk-hVt4Yh+-%FfnqMk9Y!l=@aQ`zbDTNSt zwg(S9vs5TrQ~BBRn5qE#lQX%n81xevtw6tIv@ zG{8KD;y8*2$mM))YV7iq)J{BZtOR|mNIUq63uET^S#CCrS+;rOe2v(el$(=NPoE!i zy1mWZ*^3iXV-r(c$M1`6?GO_@2bI;wL=lg~4eehfY9zFO>1fKxL^ExvXeyS8rDEc5 zG?nOV%f#a1Y)V|4HFk@V)$)?iLAx|#oA$LOyXrLhYv+UgHDZ6wV1KP2N&=@R0e1|J zQ4$UrHd1iOq!T9ncDUvV-pIHPJN&Se^25@~4{PU*F4ske6V?yM7*E-)9J_Az4$yLIdX zRJel*2f3!ZbBOEvJMt=be}KKYLFUpBmxtlaL27w`;8Al6daLhEjw3|W^OK4=7 zyg^ks3k_Be^h~;fHNF8Zc&=dOLbZUR@}U(x0}l#z0xWg{a>^UX8KhYPJgW0ljX`5B zU(JCE^QVYf;1+&eqvlCi$vxRCA%LMpAq7N4<96 zihg8WMtJm?W9nR}N&-U*N}}5F5^bZVJdT3t472mVNvSD{$dA+J)aCK8n~vVmrQ|hg zDtpgTFzn>{cjZ1gTe8V6JUekn&6(1>)9SQwow!~{n^)N`?Lje$0+x{ZD2n%?IEMm; zSm?sc*6%0r0EiqJd&HF6X9Qt%uldZ<&@9>G(no$SDvw9Jj9EB_KDRPH4395CA-yPcDMzG;M94PL0&*1*+*U9{dkAhcniRCWf$;B`Wx1Z`v#8V8=y0<~jW!hp5zd@M*4gkA2bv4p{&2X4ZvG;9>Kiu>7->2Z zFg!EFPA-(?DkZk+ZGTiYf#-!eXtTflCVA6nIsi=vRwu_9OL{JHNb!2z9WKg)@(fR~ zwbf#^1GDUCMy8lb?r6XSi%T8`$IDY<{}SU)wk3PNd9NP~hh0D>exJNv&l+8VW!ZpX z<#kakf35+;{CFdV8DR`_U*LRqElTbkXKX z<^YO8awkmgj9I(BGkEgE)#eb2!?@ZX!eD)wM=6wU0ao-?a+S@)KaFl9p-Rdc9s=qO zXIIEWK;^7CBv{20pF3)4Qr;^DESJqv-2J*yt!}KYZd9?EEz2sHD<*??4;-zgt1zpT zvBXDM6L@N1j*ntV=dp5SA@2l>f6Qa{ohZO#^R93>4q)yZq)!?&#J|w6So3Za_n`0q zSo2;KCs3S3aUY8N!{G=}%5UKT5$1@$%x^x3;!zZ^G$)2xGv0*)tFuA2lLnIVb!@fr z1Tm^^Bef6ng|uWh1g1i8<^eUpOPyCTXr4j^Lh&$)pCH#fnVd4mCmuJk=4$>V3K&f$ z)-Qd&ZZC5oL+Jl1sP^H~Of(fq#Zqmelz*e*m&_znaj}<3Wjhm@DC)&sKvhDfSp}Pd z%RX#cT`ZTX_6)iIfk)tb^-W`aaV~Rh9Q*_HF)m|U5;C?WDPvnw9NPkDHSI#Hc8csI zN3$gP-f1q|xy*2xC2No&RD-e9jKWg03QO%!0WF;hNbOQU>MjMO?p8pm41?)YVK94D z7)(DurI(+w578<($l)&7IKWXc#DOgPc;g@k!w`aGfXN=>%kSXJ2bevB%$^~#2N;u; zNUL>7a%Dhrg_V5zuv^DILWQGLI3_V!Z^zJgoG^waEdk6z48T%5{{j!#ZKVV>c0XV0CUV&G%!^3>P_Vq{QzgYhvw zoWS-C2j0lg7&&I~@PLPeYo0e)U-b~0M?-o0K?SKWj;d`zDLdjEC4&bE&oNd8PHDA?8%;_n>@O5D45JJ`w)+j zc%4q;|KgFG!0d8iHy<5TFD!;Zo4~pk-FY-z6-sJPj(Y9B74=bAw?~gTqt1n@ItuGn z?PxQ$M{R5rOk$Y*=EK~IzLc>!m9fKax`Me?!#kRb-LupJnEMI}JlXsZia8WlQCQRh zIcC3P7g%&Vz}(&(qp3~k9EFqDYfy_}?xW0wT9{j(Me6Ww<{aCI?+1gT9@D}`x(|YL zO_A=H;@1dyNVk-Zge-0e?|Ta8~+bNm`yD?p+p@-!g|j+0wla?X^?Vs0xnA1Oja zt55o4Z)?$NV++x0pZD0((CTMZFU;CrFYx4t%XS1zJz~(>FtrROLjytZ_F&a9k`~39 zST!6;eMI*`Yi0UK(3-ybzYZX33n^$4T8H{iE%$6?)$gos=5{!tOW&gKyc)y=y%(?&~GT* zWzmcacD0(HZ5}s#Uu2EAVR93Ik>?q4(|)HpwG$D;Cpe7QU`#uvVujmOtZ-b#3MW*o za8lvhDTQyR6~5iB@a>Gkx3dc0?ojx4r^2_p6u!Mn;oA}e-={F}{R#s=pfK<|R4V8} z1%Mw?0Qi6czy}ooJ_NuS8;6<315D!)#=ynFqd>f|ag3}(ym2R41H3V;qL~L7Ilqf9 zAL7f8lXaM;?F5%6&{oTyWDvf-^AZJ;_W=hH}uaO(|6>}-Tj9chace{ zKgH$4z~M1$@j%CO2_xz=b9txHI>T@>OFlGX)j-aXCXh1^oV;e%V%&Mohb>Y%^6b%v zLC7I@hUQ5?vzad_GQzZ(88kx_XSML6UG&h42E)Gz;L@_@77JBp!E0`VAhmnV6(ZB$ z*koTkLYhb)756Rs6Db=M>c=gH)UHJQM!&}jVzqCoqTOME3>f(L35`QfymW4H5`g=Os}jf_C`^oE~6{)Z^)yu`m8(A=BoBqv8*DO=aW1>a-t zQ$O5XDK`MT!!`r1%5<1Z>UgM9gP{hXJKAsqRDr~e0q*MyE`I?9(->xlv1iGsI|zKq zpiLp}FNED}^oo*XGmt{OXoyh+272)R-Sur$SW{OterE zw;8W(PET&SFol#GNnx0shWS9Mh)dotY*I^Zgxzvp#>}IP;1dV=u zqq-4#9vFvsesL>QqLm3b=fr*A*m@0UagH7b-M+a=xqm$D#v(ZQ4{8JnoQ$bv0&rXj z2vJzRj}vxyIe3-VZAPCtpq7v2qPcp-3a`@KNl)Qen;x+cC%KZdS{v1n)a#_UPP)EM zyIv>5b+Yw!I`lf7T&Jr^bObzY86Ckmdj$p3tYvidiztwlY(9_TV<f2h$qHEVI7lDl7y_(HMX>gvUvurekO>M7v5?1A5Jt?Er}`Q)6NSY`P-WDIL5GR)+V`1KOrER<>pIcY%%3NQTBg`mJktP4G1e(!|PlW&2W zfuSd8lefXvs}u&#gA%gwy@AP>|vbyP~G z%H0&|)uG~9yyzBIw%VLd?*<9?27P+%WV70-hT4Xk0V+Ef4ALTJFx&fDBnNPWbZ9CR zq&?f^Ra%z3)e#^>T-EhaO>J;kV39B?C@Fy{yf ziK|JHRrqSe4TlnSMfaZ)cjY05g-#2i@~)CYa(%#6Os|`8ha))@HPlV$b(7kClZ|y# zdfk*(H(gg3fn1QA3IFwv?1B#;weI3k`MH91w93(2JkeGGo56CDAzqLy7e$l@a2mKt zv$SP4ba~wRX%uw^Zjsg+w>WT}rLZ>kOTI3;44K zCd33jXmofHV(&zXiTn0|QxWErZ?pLsm;7RLEdiPx>=hL3Ov`*|4!rUs%GT*0Y0+cs z&sV*NoI%0k;aTsQJSehd^rF`U9wXUM`k2=u9xd5!V!!`ZGp$Vwx{R#PL%$e>oOg9g z9)@0-=%K)Hv?&xVj7eJ*_x%t?Yw)V(Rh2RVEtWD;5q>Qt{?$gK)&GsAlBv#2GL=px zQeCOu&Q6X_bMK^L)aoPEw0N~{s*6`^mavWJ#eje{PS~DfBhW$!6~Faz1S#7Q8bK7B zj7N);ogkZsY6P|IY&L+dt(keWyU4^G*RCroeg{O6>@U%`9p4O$p({}MayVMIX zWrSL{ics65BGh_Rgjz2m)I@_3o+e*_DO0rN3os2tx8b#x5&nDu=<1hrF+}6O7c)Zk zek$%*kQ+PjyZi%kjiT`CS5j6D%)3U7R{MX3B$` zPn3q4lhgWn(#bnZ)s|*WL*oNllwYtP!SIJ?Z)IScd>N#CNUP)9&VayMi*Nf3h3kOI z1!~DYgt$HC^-+V@-2|j>Ws4Ez_IPutS|iGhTkQ);I7WbcRTOwFp80D;tz74M60ArvE95$lx31$USH)*YNb5R*>mAG^ zZ^$=)2FGHxe0vqe&xXT1nCR1B-fm`I{Q`z3SKe#@?XlMJy_L`x4)UNP##F@p1p_ZN zEJrt6FgO*sU@%a#VBiXYgjThs0?P<`IV1P0-`A`%g<39!jl0|6l3HGoxO-4OE96iP z;j6U>I*)gsr*KC;_8jNe8tvfgZU`m!a0Lw|xBSQu3?;Jre9W2q5SVidVdRoO&Pf=# z5io}TzFeVt!1zP!0*s9{LD?wg1`U*LYXoIA6<{86-s1p@K@>b!;OvfoSgZcPR|B!u zB$(FVsaReJ2~UlxMUfzU5{8L10(6K{Gp!b|+YB}|UqkV8D1JU1ZUgJN5piw9AiVHx zjSs%+4{Z_#gf%t6uW8dDzh6edW74Nkd>X~Cpy1R|ALwJF?6c}N=I2nb-eK6@5HQC+ z_PlFs?yyAAUlR9y0DL<~ULvTQ1nPbfMu*+-U6_P866f^KbZ2`es^ByQqcMC2##~;& zM?Fi{O}`a$JaW@-#V}N684Wp~5+Rd0pLav7fmCHV5mW+WF$IjZDPSzFfH9c}x?3S_ z-3n>jqmZ^9g|zi5q^(aOZF?2c)~}GZeJT-jzk=BgD46XI1+yJgFxw%O2s)q=K?hYL z=#WYTJjcBk7kgCP_f0evPr0Qi;nQCGeSU`nTJUnsSA2eNeUz|ITbO-=hW{bR+C-G_ z>989+D(?GLPmtEz%$LG$=F8%~f4U{|zUs97Q*<-dDqJ zEEadau~B*F1>5Re`GxQ^r0~?g+zP?>GNhOe^S9LTFg3u-kU|^2xt*HuItr#S%ntK2 zq(IU`uh@!FQE8s6kkTc;3LD392E#5ZsC^%|0P1|g}~|+%MH1JH9h*U5wvD3MNuxW zg!Q^*9B-BZ81t@dmJ8S{12FDg*(?{ZSq5OzyD}&jFmUA?Lyh(wAI|~v4N!mfZRJP{ z4EV>8loKo|L9dQ(VLsmhaAN&mPHc5LAGooOZzleTwAFdZsayYMA}d6b+VP;;iDtEv zLA6uOYNs1&tK2<*;*~}P=LB$`o}bmUalHufRoKa>H-f5 z((o{Ed(+!LkqR4;W8p2mms}enYU{S?bz@rHo2AsXX?1UwQm3c&1*O!nBm{iNGevrO z9QFXA$vr=^d(GrIHflI|ZbKssdLk$aCIkV3+&lsSlCL0%8}(^%Z~N4^bKp*dxg(8S zY&TDhV@H$SOLi&)Q{y)5V%+3IqWnxAqu2uZgx3Tfve+Q`lGh?0!Pq`w|94AL1#$!Y(qwx}n&FXN5 z=g#fe=>MuGH!AwShVdPwNB?uMzHGbKy4Lv9FEli}#(TyYO|-d9VY|E^4iM-y8|X7K zM33$xb*+q&*87+um)}qIe%q-=zs+}gBCtXqX378x_HA%%M|dwUfh`h# zbG;W8+s^IwJe39oj}lw+Orl&Q;s&?amVUKHUC{WqtKIxbU*6d~o*3p88s7@`ZQ2dl zn>#GZ`*+2CZIBAZ5r!j7!dM2;+=%Kxa>|9m))h}vUl-Qvg$PlGBmqeg=X(j zq1inuG`m;DO7B*$Iq2sYZTXsmJsj5Equyr_MU1wE$Y}nU{fNb%vL6vYSeg6XIyS%K zpdi0K@9rGp`u>i*%H1De`*V=FG{og$*q;pcu;AP#(am4*$C{lU-TVfcil^LCaD(SJ zhi`jAC4a}~_tpnD__T%DH)!}*K-MOL8%~Ga*imucKk@`=z0C|gU<8QDpKOV|w{VI1 zoVf2-w^QC*7tHguP0D*qf_eVDxgb4D+Z)WI&352OV1Ug|h?)5dut=_WzO_F~?o9!$ zRD77>-;7d=KTFOoz*{GVBDAG>hC|AFwWRqs6fq-}g0D#bz{)zU%!^ zIV!g%#Nr43ytvgLDz6W&(B@BX)!P41xfaaO;&6o@`aRp)&y%kmC$O>4llvS`f}H~> z22t?*$AJMn^`Y{Pz|#IPN^5_Ve4D>V9=z^f_dRm((pz(HTt{c&=7HT`N8d3kty!f8BX;D9^qba>dWBq z4SgA00gdmhD(_q@uQR4>inr*h@}3yIbyt=9(D=9Hs`7qMZr)O>%7+@7UE>4bj3(OL zrm(I2!vO-lW&?djhG=*nt7~PA!HsF|?YXLaSB!3_Rpt9?)aC7LZ$9M9`|Z4{{CncQ zS&$0FSv8y-ugN<`SAZ zQ!c8n0`Oo8xi+6bfe#q)3)MJylNa6DXl6$7OW<<3e6g6zh15a(mWB5$l&z(r{RqYd jwm7@QK!ew|rNv+T`p@@Q`d9nE-#^>`?fwz5ed_-KxWFt_ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.py b/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.py new file mode 100755 index 0000000..b97dd86 --- /dev/null +++ b/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub version of the XMPP API, writes messages to logs.""" + + + + + +import logging +import os + +from google.appengine.api import apiproxy_stub +from google.appengine.api import xmpp +from google.appengine.api.xmpp import xmpp_service_pb + + +class XmppServiceStub(apiproxy_stub.APIProxyStub): + """Python only xmpp service stub. + + This stub does not use an XMPP network. It prints messages to the console + instead of sending any stanzas. + """ + + def __init__(self, log=logging.info, service_name='xmpp'): + """Initializer. + + Args: + log: A logger, used for dependency injection. + service_name: Service name expected for all calls. + """ + super(XmppServiceStub, self).__init__(service_name) + self.log = log + + def _Dynamic_GetPresence(self, request, response): + """Implementation of XmppService::GetPresence. + + Returns online if the first character of the JID comes before 'm' in the + alphabet, otherwise returns offline. + + Args: + request: A PresenceRequest. + response: A PresenceResponse. + """ + jid = request.jid() + self._GetFrom(request.from_jid()) + if jid[0] < 'm': + response.set_is_available(True) + else: + response.set_is_available(False) + + def _Dynamic_SendMessage(self, request, response): + """Implementation of XmppService::SendMessage. + + Args: + request: An XmppMessageRequest. + response: An XmppMessageResponse . + """ + from_jid = self._GetFrom(request.from_jid()) + self.log('Sending an XMPP Message:') + self.log(' From:') + self.log(' ' + from_jid) + self.log(' Body:') + self.log(' ' + request.body()) + self.log(' Type:') + self.log(' ' + request.type()) + self.log(' Raw Xml:') + self.log(' ' + str(request.raw_xml())) + self.log(' To JIDs:') + for jid in request.jid_list(): + self.log(' ' + jid) + + for jid in request.jid_list(): + response.add_status(xmpp_service_pb.XmppMessageResponse.NO_ERROR) + + def _Dynamic_SendInvite(self, request, response): + """Implementation of XmppService::SendInvite. + + Args: + request: An XmppInviteRequest. + response: An XmppInviteResponse . + """ + from_jid = self._GetFrom(request.from_jid()) + self.log('Sending an XMPP Invite:') + self.log(' From:') + self.log(' ' + from_jid) + self.log(' To: ' + request.jid()) + + def _GetFrom(self, requested): + """Validates that the from JID is valid. + + Args: + requested: The requested from JID. + + Returns: + string, The from JID. + + Raises: + xmpp.InvalidJidError if the requested JID is invalid. + """ + + appid = os.environ.get('APPLICATION_ID', '') + if requested == None or requested == '': + return appid + '@appspot.com/bot' + + node, domain, resource = ('', '', '') + at = requested.find('@') + if at == -1: + self.log('Invalid From JID: No \'@\' character found. JID: %s', requested) + raise xmpp.InvalidJidError() + + node = requested[:at] + rest = requested[at+1:] + + if rest.find('@') > -1: + self.log('Invalid From JID: Second \'@\' character found. JID: %s', + requested) + raise xmpp.InvalidJidError() + + slash = rest.find('/') + if slash == -1: + domain = rest + resource = 'bot' + else: + domain = rest[:slash] + resource = rest[slash+1:] + + if resource.find('/') > -1: + self.log('Invalid From JID: Second \'/\' character found. JID: %s', + requested) + raise xmpp.InvalidJidError() + + if domain == 'appspot.com' and node == appid: + return node + '@' + domain + '/' + resource + elif domain == appid + '.appspotchat.com': + return node + '@' + domain + '/' + resource + + self.log('Invalid From JID: Must be appid@appspot.com[/resource] or ' + 'node@appid.appspotchat.com[/resource]. JID: %s', requested) + raise xmpp.InvalidJidError() diff --git a/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.pyc b/google_appengine/google/appengine/api/xmpp/xmpp_service_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b70cfd6af1515c158ad5c94df58105c6e4d67984 GIT binary patch literal 4908 zcwWU=UvC@75#Kxh6KTno>?(@Wv|S_!#0P^!QnU|55lD%WfM=RF_2!G073fHkCYFPm%Q|Y6n*GhJ2SidYs=0<k>OF zv!ud$6*6&yNMhEKHMQEEMt z3Ui_Ivj%S)p5-z%s}4dB_EoA?B0!c*wGmN_uGJ!qWjX{oQ;;5|Uq!kB905A0UvP)U z-}`9lo9H(9&tq2*I-g0OUHR;5k9k)_3Jd6kZrw-b?_g-B(#R;0Ux}Pf!TDi6)GeBE z3|osI;{S#se}LzS`9S4778#f&(*6`oeJuJ0aL{mjX0b}6v1oA@CXOpEE;HbDGNL5m zeHgUecwEQWn-Ur}$wVIDnG7N>3?&#Dh6X8MO1&^ciWbQrDC26ARtv>-gaZxwFEBJm zYAl**KaW)NNL{3fielXyDm6?*7-evnp-hD%G#3OwXcME|TtOj)Jevk+>prSLTUg~4 zkK%y~e-@7b2JpP3Ji0Dm2&6|8A1M?V3dwyy=3~ax4iF%01w0^rO>fT$B_aTDyW}eU z9}M3(OT<{DCNeG+mvW=k`m->dTmXdn!bLX_W|F5GfeZ)a(&p|!=GyT7D9R&>p-G|MB=;|6nJd|W34pz9yY*dhkb{b8=gv>3o7#4p=~5fe%sM(2@C zqSHhWj85{2AmDu%C0dYk15A8_gR_=I&Fl(lGj#pT5Hr8R(;Q6!(y|{eMe}V`i&n7O zrgzWV@pgOyU&;Lle7oQu1e}GS=K$wWNOuF_&o2Ds1+Wy@-V&j>_SHi~g1lv(($hS} zb)NDn4|pBf%FeW9&I)l}zC{cbWH|bAk6jnpLP61Pc=7}L_PQc#oMXlQkx9 zk+diOW}n0xkUv#)&);91KV7JD4{eoxE|62&<~VN#^BS9_bIYh-1#`OdVs9@E$J)|p zFmfTPTe{*_xNY|3Jrz$Ed?(Wkc5S*<5M6-gWI;GlnA^H_Tt`Z93kRA}Dq#Ygf;gHO zkVMI)O>Pq+x(F}F2_*!iRhUS~4uoP9$AH_&Omu+t&)j47rd~TQZ9IMyeh>tYf;xhg zk}bgW40h(0{4y!F6mV)J-azG*U2hBi?!a%){eFWKR`zy%9Ord^eh+`n{*S4*nr)D0 zT`GC{TlSLx+)mG-ANvXTwTv%$=@Yr$DWIRq3(^ z4D5Bic$uc;{G z&I;T6-F1asRTeT)T5(z0#*NbKt}0mqTpKe>w&@#~8`sd{^ggZOJI8WNGpiX+Q(DM) zO7pE7HN%!oQuPM)O;^@yyS9mf>j19=5V{BA!Gl+H3B+4$@3~x7>FsAJg&1|~2siyB z41a|_IgapYV@8p&jT1nc+9c@x&++rkX(r+pyb;7=Ip4h6n$5OBy@h0YKzr6XBj~i| zjELF>fD+YzB;ybA9J*`Q*e#SfBW3E$#GU3goa(UC`MCZ5;Ys_^ao9dGluejs2aq6; zb4H(4X3gFa2hGAQ95L>;`;~tOKs#pP13kwKfa|U3ebOe=GLx&$4~s+ z?WDg+R}onA0y)i_ouK;vY_+O+JN3U6#ps*+HK-p-!qv-WOkT z@P4XdaX?J#f|nTQ79G=zCiv~jK#y&M6-8;fcLkK4T&3iIp)hngAFI^1umhRKwqVX5 z&TELEp2xPeIGY258ecM%1fk^y1;uI{!7J6Zy<{<26IsnU_Qdp9OCmjb8HJc`h9RnW z7Zq*odarp^|BiPDYVWqU?bp29{wrS9tH2y-Wv{kEd(GR08ole|s*Z)*JudON2#?De zEygOIB)F{5GFE*z$0snPBb{BQ14WO&;4Tio#^H75?yiL^er|st5ICy9tFT-u`*&;A zo$9OAEjnHB4i3M^0h<_VbF6~KTmj4^zEB-%?%%-DK=rn#Lsf^Jb_f622LI`WjCg5B XrilH?#^~L<^P`tHy-j}`K~VS~4}X_; literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/yaml_builder.py b/google_appengine/google/appengine/api/yaml_builder.py new file mode 100755 index 0000000..71e730c --- /dev/null +++ b/google_appengine/google/appengine/api/yaml_builder.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""PyYAML event builder handler + +Receives events from YAML listener and forwards them to a builder +object so that it can construct a properly structured object. +""" + + + + + +from google.appengine.api import yaml_errors +from google.appengine.api import yaml_listener + +import yaml + +_TOKEN_DOCUMENT = 'document' +_TOKEN_SEQUENCE = 'sequence' +_TOKEN_MAPPING = 'mapping' +_TOKEN_KEY = 'key' +_TOKEN_VALUES = frozenset(( + _TOKEN_DOCUMENT, + _TOKEN_SEQUENCE, + _TOKEN_MAPPING, + _TOKEN_KEY)) + + +class Builder(object): + """Interface for building documents and type from YAML events. + + Implement this interface to create a new builder. Builders are + passed to the BuilderHandler and used as a factory and assembler + for creating concrete representations of YAML files. + """ + + def BuildDocument(self): + """Build new document. + + The object built by this method becomes the top level entity + that the builder handler constructs. The actual type is + determined by the sub-class of the Builder class and can essentially + be any type at all. This method is always called when the parser + encounters the start of a new document. + + Returns: + New object instance representing concrete document which is + returned to user via BuilderHandler.GetResults(). + """ + + def InitializeDocument(self, document, value): + """Initialize document with value from top level of document. + + This method is called when the root document element is encountered at + the top level of a YAML document. It should get called immediately + after BuildDocument. + + Receiving the None value indicates the empty document. + + Args: + document: Document as constructed in BuildDocument. + value: Scalar value to initialize the document with. + """ + + def BuildMapping(self, top_value): + """Build a new mapping representation. + + Called when StartMapping event received. Type of object is determined + by Builder sub-class. + + Args: + top_value: Object which will be new mappings parant. Will be object + returned from previous call to BuildMapping or BuildSequence. + + Returns: + Instance of new object that represents a mapping type in target model. + """ + + def EndMapping(self, top_value, mapping): + """Previously constructed mapping scope is at an end. + + Called when the end of a mapping block is encountered. Useful for + additional clean up or end of scope validation. + + Args: + top_value: Value which is parent of the mapping. + mapping: Mapping which is at the end of its scope. + """ + + def BuildSequence(self, top_value): + """Build a new sequence representation. + + Called when StartSequence event received. Type of object is determined + by Builder sub-class. + + Args: + top_value: Object which will be new sequences parant. Will be object + returned from previous call to BuildMapping or BuildSequence. + + Returns: + Instance of new object that represents a sequence type in target model. + """ + + def EndSequence(self, top_value, sequence): + """Previously constructed sequence scope is at an end. + + Called when the end of a sequence block is encountered. Useful for + additional clean up or end of scope validation. + + Args: + top_value: Value which is parent of the sequence. + sequence: Sequence which is at the end of its scope. + """ + + def MapTo(self, subject, key, value): + """Map value to a mapping representation. + + Implementation is defined by sub-class of Builder. + + Args: + subject: Object that represents mapping. Value returned from + BuildMapping. + key: Key used to map value to subject. Can be any scalar value. + value: Value which is mapped to subject. Can be any kind of value. + """ + + def AppendTo(self, subject, value): + """Append value to a sequence representation. + + Implementation is defined by sub-class of Builder. + + Args: + subject: Object that represents sequence. Value returned from + BuildSequence + value: Value to be appended to subject. Can be any kind of value. + """ + + +class BuilderHandler(yaml_listener.EventHandler): + """PyYAML event handler used to build objects. + + Maintains state information as it receives parse events so that object + nesting is maintained. Uses provided builder object to construct and + assemble objects as it goes. + + As it receives events from the YAML parser, it builds a stack of data + representing structural tokens. As the scope of documents, mappings + and sequences end, those token, value pairs are popped from the top of + the stack so that the original scope can resume processing. + + A special case is made for the _KEY token. It represents a temporary + value which only occurs inside mappings. It is immediately popped off + the stack when it's associated value is encountered in the parse stream. + It is necessary to do this because the YAML parser does not combine + key and value information in to a single event. + """ + + def __init__(self, builder): + """Initialization for builder handler. + + Args: + builder: Instance of Builder class. + + Raises: + ListenerConfigurationError when builder is not a Builder class. + """ + if not isinstance(builder, Builder): + raise yaml_errors.ListenerConfigurationError( + 'Must provide builder of type yaml_listener.Builder') + self._builder = builder + self._stack = None + self._top = None + self._results = [] + + def _Push(self, token, value): + """Push values to stack at start of nesting. + + When a new object scope is beginning, will push the token (type of scope) + along with the new objects value, the latter of which is provided through + the various build methods of the builder. + + Args: + token: Token indicating the type of scope which is being created; must + belong to _TOKEN_VALUES. + value: Value to associate with given token. Construction of value is + determined by the builder provided to this handler at construction. + """ + self._top = (token, value) + self._stack.append(self._top) + + def _Pop(self): + """Pop values from stack at end of nesting. + + Called to indicate the end of a nested scope. + + Returns: + Previously pushed value at the top of the stack. + """ + assert self._stack != [] and self._stack is not None + token, value = self._stack.pop() + if self._stack: + self._top = self._stack[-1] + else: + self._top = None + return value + + def _HandleAnchor(self, event): + """Handle anchor attached to event. + + Currently will raise an error if anchor is used. Anchors are used to + define a document wide tag to a given value (scalar, mapping or sequence). + + Args: + event: Event which may have anchor property set. + + Raises: + NotImplementedError if event attempts to use an anchor. + """ + if hasattr(event, 'anchor') and event.anchor is not None: + raise NotImplementedError, 'Anchors not supported in this handler' + + def _HandleValue(self, value): + """Handle given value based on state of parser + + This method handles the various values that are created by the builder + at the beginning of scope events (such as mappings and sequences) or + when a scalar value is received. + + Method is called when handler receives a parser, MappingStart or + SequenceStart. + + Args: + value: Value received as scalar value or newly constructed mapping or + sequence instance. + + Raises: + InternalError if the building process encounters an unexpected token. + This is an indication of an implementation error in BuilderHandler. + """ + token, top_value = self._top + + if token == _TOKEN_KEY: + key = self._Pop() + mapping_token, mapping = self._top + assert _TOKEN_MAPPING == mapping_token + self._builder.MapTo(mapping, key, value) + + elif token == _TOKEN_MAPPING: + self._Push(_TOKEN_KEY, value) + + elif token == _TOKEN_SEQUENCE: + self._builder.AppendTo(top_value, value) + + elif token == _TOKEN_DOCUMENT: + self._builder.InitializeDocument(top_value, value) + + else: + raise yaml_errors.InternalError('Unrecognized builder token:\n%s' % token) + + def StreamStart(self, event, loader): + """Initializes internal state of handler + + Args: + event: Ignored. + """ + assert self._stack is None + self._stack = [] + self._top = None + self._results = [] + + def StreamEnd(self, event, loader): + """Cleans up internal state of handler after parsing + + Args: + event: Ignored. + """ + assert self._stack == [] and self._top is None + self._stack = None + + def DocumentStart(self, event, loader): + """Build new document. + + Pushes new document on to stack. + + Args: + event: Ignored. + """ + assert self._stack == [] + self._Push(_TOKEN_DOCUMENT, self._builder.BuildDocument()) + + def DocumentEnd(self, event, loader): + """End of document. + + Args: + event: Ignored. + """ + assert self._top[0] == _TOKEN_DOCUMENT + self._results.append(self._Pop()) + + def Alias(self, event, loader): + """Not implemented yet. + + Args: + event: Ignored. + """ + raise NotImplementedError('Anchors not supported in this handler') + + def Scalar(self, event, loader): + """Handle scalar value + + Since scalars are simple values that are passed directly in by the + parser, handle like any value with no additional processing. + + Of course, key values will be handles specially. A key value is recognized + when the top token is _TOKEN_MAPPING. + + Args: + event: Event containing scalar value. + """ + self._HandleAnchor(event) + if event.tag is None and self._top[0] != _TOKEN_MAPPING: + try: + tag = loader.resolve(yaml.nodes.ScalarNode, + event.value, event.implicit) + except IndexError: + tag = loader.DEFAULT_SCALAR_TAG + else: + tag = event.tag + + if tag is None: + value = event.value + else: + node = yaml.nodes.ScalarNode(tag, + event.value, + event.start_mark, + event.end_mark, + event.style) + value = loader.construct_object(node) + self._HandleValue(value) + + def SequenceStart(self, event, loader): + """Start of sequence scope + + Create a new sequence from the builder and then handle in the context + of its parent. + + Args: + event: SequenceStartEvent generated by loader. + loader: Loader that generated event. + """ + self._HandleAnchor(event) + token, parent = self._top + + if token == _TOKEN_KEY: + token, parent = self._stack[-2] + sequence = self._builder.BuildSequence(parent) + self._HandleValue(sequence) + self._Push(_TOKEN_SEQUENCE, sequence) + + def SequenceEnd(self, event, loader): + """End of sequence. + + Args: + event: Ignored + loader: Ignored. + """ + assert self._top[0] == _TOKEN_SEQUENCE + end_object = self._Pop() + top_value = self._top[1] + self._builder.EndSequence(top_value, end_object) + + def MappingStart(self, event, loader): + """Start of mapping scope. + + Create a mapping from builder and then handle in the context of its + parent. + + Args: + event: MappingStartEvent generated by loader. + loader: Loader that generated event. + """ + self._HandleAnchor(event) + token, parent = self._top + + if token == _TOKEN_KEY: + token, parent = self._stack[-2] + mapping = self._builder.BuildMapping(parent) + self._HandleValue(mapping) + self._Push(_TOKEN_MAPPING, mapping) + + def MappingEnd(self, event, loader): + """End of mapping + + Args: + event: Ignored. + loader: Ignored. + """ + assert self._top[0] == _TOKEN_MAPPING + end_object = self._Pop() + top_value = self._top[1] + self._builder.EndMapping(top_value, end_object) + + def GetResults(self): + """Get results of document stream processing. + + This method can be invoked after fully parsing the entire YAML file + to retrieve constructed contents of YAML file. Called after EndStream. + + Returns: + A tuple of all document objects that were parsed from YAML stream. + + Raises: + InternalError if the builder stack is not empty by the end of parsing. + """ + if self._stack is not None: + raise yaml_errors.InternalError('Builder stack is not empty.') + return tuple(self._results) diff --git a/google_appengine/google/appengine/api/yaml_builder.pyc b/google_appengine/google/appengine/api/yaml_builder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d4fc2b0f1f0763da1318dbb94cc2df1e62b3be7 GIT binary patch literal 15951 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 A new document has been started + A new document has been started + + In the example above, the implemented handler class (PrintDocumentHandler) + has a single method which reports each time a new document is started within + a YAML file. It is not necessary to subclass the EventListener, merely it + receives a PrintDocumentHandler instance. Every time a new document begins, + PrintDocumentHandler.DocumentStart is called with the PyYAML event passed + in as its parameter.. + """ + + def __init__(self, event_handler): + """Initialize PyYAML event listener. + + Constructs internal mapping directly from event type to method on actual + handler. This prevents reflection being used during actual parse time. + + Args: + event_handler: Event handler that will receive mapped events. Must + implement at least one appropriate handler method named from + the values of the _EVENT_METHOD_MAP. + + Raises: + ListenerConfigurationError if event_handler is not an EventHandler. + """ + if not isinstance(event_handler, EventHandler): + raise yaml_errors.ListenerConfigurationError( + 'Must provide event handler of type yaml_listener.EventHandler') + self._event_method_map = {} + for event, method in _EVENT_METHOD_MAP.iteritems(): + self._event_method_map[event] = getattr(event_handler, method) + + def HandleEvent(self, event, loader=None): + """Handle individual PyYAML event. + + Args: + event: Event to forward to method call in method call. + + Raises: + IllegalEvent when receives an unrecognized or unsupported event type. + """ + if event.__class__ not in _EVENT_METHOD_MAP: + raise yaml_errors.IllegalEvent( + "%s is not a valid PyYAML class" % event.__class__.__name__) + if event.__class__ in self._event_method_map: + self._event_method_map[event.__class__](event, loader) + + def _HandleEvents(self, events): + """Iterate over all events and send them to handler. + + This method is not meant to be called from the interface. + + Only use in tests. + + Args: + events: Iterator or generator containing events to process. + raises: + EventListenerParserError when a yaml.parser.ParserError is raised. + EventError when an exception occurs during the handling of an event. + """ + for event in events: + try: + self.HandleEvent(*event) + except Exception, e: + event_object, loader = event + raise yaml_errors.EventError(e, event_object) + + def _GenerateEventParameters(self, + stream, + loader_class=yaml.loader.SafeLoader): + """Creates a generator that yields event, loader parameter pairs. + + For use as parameters to HandleEvent method for use by Parse method. + During testing, _GenerateEventParameters is simulated by allowing + the harness to pass in a list of pairs as the parameter. + + A list of (event, loader) pairs must be passed to _HandleEvents otherwise + it is not possible to pass the loader instance to the handler. + + Also responsible for instantiating the loader from the Loader + parameter. + + Args: + stream: String document or open file object to process as per the + yaml.parse method. Any object that implements a 'read()' method which + returns a string document will work. + Loader: Loader class to use as per the yaml.parse method. Used to + instantiate new yaml.loader instance. + + Yields: + Tuple(event, loader) where: + event: Event emitted by PyYAML loader. + loader_class: Used for dependency injection. + """ + assert loader_class is not None + try: + loader = loader_class(stream) + while loader.check_event(): + yield (loader.get_event(), loader) + except yaml.error.YAMLError, e: + raise yaml_errors.EventListenerYAMLError(e) + + def Parse(self, stream, loader_class=yaml.loader.SafeLoader): + """Call YAML parser to generate and handle all events. + + Calls PyYAML parser and sends resulting generator to handle_event method + for processing. + + Args: + stream: String document or open file object to process as per the + yaml.parse method. Any object that implements a 'read()' method which + returns a string document will work with the YAML parser. + loader_class: Used for dependency injection. + """ + self._HandleEvents(self._GenerateEventParameters(stream, loader_class)) diff --git a/google_appengine/google/appengine/api/yaml_listener.pyc b/google_appengine/google/appengine/api/yaml_listener.pyc new file mode 100644 index 0000000000000000000000000000000000000000..372f6518d81ef001d384e0279b7032a97abaaa07 GIT binary patch literal 9472 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/api/yaml_object.py b/google_appengine/google/appengine/api/yaml_object.py new file mode 100755 index 0000000..767f1f3 --- /dev/null +++ b/google_appengine/google/appengine/api/yaml_object.py @@ -0,0 +1,294 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Builder for mapping YAML documents to object instances. + +ObjectBuilder is responsible for mapping a YAML document to classes defined +using the validation mechanism (see google.appengine.api.validation.py). +""" + + + + + +from google.appengine.api import validation +from google.appengine.api import yaml_listener +from google.appengine.api import yaml_builder +from google.appengine.api import yaml_errors + +import yaml + + +class _ObjectMapper(object): + """Wrapper used for mapping attributes from a yaml file to an object. + + This wrapper is required because objects do not know what property they are + associated with a creation time, and therefore can not be instantiated + with the correct class until they are mapped to their parents. + """ + + def __init__(self): + """Object mapper starts off with empty value.""" + self.value = None + self.seen = set() + + def set_value(self, value): + """Set value of instance to map to. + + Args: + value: Instance that this mapper maps to. + """ + self.value = value + + def see(self, key): + if key in self.seen: + raise yaml_errors.DuplicateAttribute("Duplicate attribute '%s'." % key) + self.seen.add(key) + +class _ObjectSequencer(object): + """Wrapper used for building sequences from a yaml file to a list. + + This wrapper is required because objects do not know what property they are + associated with a creation time, and therefore can not be instantiated + with the correct class until they are mapped to their parents. + """ + + def __init__(self): + """Object sequencer starts off with empty value.""" + self.value = [] + self.constructor = None + + def set_constructor(self, constructor): + """Set object used for constructing new sequence instances. + + Args: + constructor: Callable which can accept no arguments. Must return + an instance of the appropriate class for the container. + """ + self.constructor = constructor + + +class ObjectBuilder(yaml_builder.Builder): + """Builder used for constructing validated objects. + + Given a class that implements validation.Validated, it will parse a YAML + document and attempt to build an instance of the class. It does so by mapping + YAML keys to Python attributes. ObjectBuilder will only map YAML fields + to attributes defined in the Validated subclasses 'ATTRIBUTE' definitions. + Lists are mapped to validated. Repeated attributes and maps are mapped to + validated.Type properties. + + For a YAML map to be compatible with a class, the class must have a + constructor that can be called with no parameters. If the provided type + does not have such a constructor a parse time error will occur. + """ + + def __init__(self, default_class): + """Initialize validated object builder. + + Args: + default_class: Class that is instantiated upon the detection of a new + document. An instance of this class will act as the document itself. + """ + self.default_class = default_class + + def _GetRepeated(self, attribute): + """Get the ultimate type of a repeated validator. + + Looks for an instance of validation.Repeated, returning its constructor. + + Args: + attribute: Repeated validator attribute to find type for. + + Returns: + The expected class of of the Type validator, otherwise object. + """ + if isinstance(attribute, validation.Optional): + attribute = attribute.validator + if isinstance(attribute, validation.Repeated): + return attribute.constructor + return object + + def BuildDocument(self): + """Instantiate new root validated object. + + Returns: + New instance of validated object. + """ + return self.default_class() + + def BuildMapping(self, top_value): + """New instance of object mapper for opening map scope. + + Args: + top_value: Parent of nested object. + + Returns: + New instance of object mapper. + """ + result = _ObjectMapper() + if isinstance(top_value, self.default_class): + result.value = top_value + return result + + def EndMapping(self, top_value, mapping): + """When leaving scope, makes sure new object is initialized. + + This method is mainly for picking up on any missing required attributes. + + Args: + top_value: Parent of closing mapping object. + mapping: _ObjectMapper instance that is leaving scope. + """ + try: + mapping.value.CheckInitialized() + except validation.ValidationError: + raise + except Exception, e: + try: + error_str = str(e) + except Exception: + error_str = '' + + raise validation.ValidationError("Invalid object:\n%s" % error_str, e) + + def BuildSequence(self, top_value): + """New instance of object sequence. + + Args: + top_value: Object that contains the new sequence. + + Returns: + A new _ObjectSequencer instance. + """ + return _ObjectSequencer() + + def MapTo(self, subject, key, value): + """Map key-value pair to an objects attribute. + + Args: + subject: _ObjectMapper of object that will receive new attribute. + key: Key of attribute. + value: Value of new attribute. + + Raises: + UnexpectedAttribute when the key is not a validated attribute of + the subject value class. + """ + assert subject.value is not None + if key not in subject.value.ATTRIBUTES: + raise yaml_errors.UnexpectedAttribute( + 'Unexpected attribute \'%s\' for object of type %s.' % + (key, str(subject.value.__class__))) + + if isinstance(value, _ObjectMapper): + value.set_value(subject.value.GetAttribute(key).expected_type()) + value = value.value + elif isinstance(value, _ObjectSequencer): + value.set_constructor(self._GetRepeated(subject.value.ATTRIBUTES[key])) + value = value.value + + subject.see(key) + try: + setattr(subject.value, key, value) + except validation.ValidationError, e: + try: + error_str = str(e) + except Exception: + error_str = '' + + try: + value_str = str(value) + except Exception: + value_str = '' + + e.message = ("Unable to assign value '%s' to attribute '%s':\n%s" % + (value_str, key, error_str)) + raise e + except Exception, e: + try: + error_str = str(e) + except Exception: + error_str = '' + + try: + value_str = str(value) + except Exception: + value_str = '' + + message = ("Unable to assign value '%s' to attribute '%s':\n%s" % + (value_str, key, error_str)) + raise validation.ValidationError(message, e) + + def AppendTo(self, subject, value): + """Append a value to a sequence. + + Args: + subject: _ObjectSequence that is receiving new value. + value: Value that is being appended to sequence. + """ + if isinstance(value, _ObjectMapper): + value.set_value(subject.constructor()) + subject.value.append(value.value) + else: + subject.value.append(value) + + +def BuildObjects(default_class, stream, loader=yaml.loader.SafeLoader): + """Build objects from stream. + + Handles the basic case of loading all the objects from a stream. + + Args: + default_class: Class that is instantiated upon the detection of a new + document. An instance of this class will act as the document itself. + stream: String document or open file object to process as per the + yaml.parse method. Any object that implements a 'read()' method which + returns a string document will work with the YAML parser. + loader_class: Used for dependency injection. + + Returns: + List of default_class instances parsed from the stream. + """ + builder = ObjectBuilder(default_class) + handler = yaml_builder.BuilderHandler(builder) + listener = yaml_listener.EventListener(handler) + + listener.Parse(stream, loader) + return handler.GetResults() + + +def BuildSingleObject(default_class, stream, loader=yaml.loader.SafeLoader): + """Build object from stream. + + Handles the basic case of loading a single object from a stream. + + Args: + default_class: Class that is instantiated upon the detection of a new + document. An instance of this class will act as the document itself. + stream: String document or open file object to process as per the + yaml.parse method. Any object that implements a 'read()' method which + returns a string document will work with the YAML parser. + loader_class: Used for dependency injection. + """ + definitions = BuildObjects(default_class, stream, loader) + + if len(definitions) < 1: + raise yaml_errors.EmptyConfigurationFile() + if len(definitions) > 1: + raise yaml_errors.MultipleConfigurationFile() + return definitions[0] diff --git a/google_appengine/google/appengine/api/yaml_object.pyc b/google_appengine/google/appengine/api/yaml_object.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2bdd5ccbc29c40ce9d95d57aa8207ff6b501c82 GIT binary patch literal 10882 zcwX&VOLH7o6~5gw8fnIoELn~fLr88>7|TJCNkLJG2nNM=f{Co8TC$U9Gii0-(X=(w zJ#_b25hb&TQ{e}|mMYk!iamQatSR^*Y}m4Z^PT&cp0V;El}Qx^cIm47KF+=OJiha2 z{p}wMtv^2#Z%4xXo1>qf&`)?Vx?!1d(LCW-L><$$X}>=b(r*GmCH_+%YHZ*BRu5GlE zf6>1+kM32rs2L6Gz6EMd!hWyaONvsZD%aifw);TeM)#hxy(-VMyx8H@ZGG*#I7aSN z1M^=6N3T(%NHsiG5FZLc!XZ^4HNit2`hkZT5$vwjvE*4={C3XtnK-RGG)?N>$G`{`MIJ4D;d#QK=eQv zEh%{|5#rU5ZItCXw1}lIhcu(-Hsl-AR&*)qN^&`%PEx)NA`p3o#XkMq1kWxDw!Kr5 zhbKxO+K=ZNI(OGFt$3aqEh|krmvpF{E}wNe`berjpc|8l4AsUC%c4ZHlclPh(aJMa zWevns$wQ=uMv?Seud@c}b5s0`n(N)Hudb(29%k2XWyfhR3*+Lt*4(z!T+?xV)Iq9z zomI_9b8u1uZQAW5P0DtA9rUrK@S9%b8%{WGKrc@N_|7RwzFO^nRr6K zSZ$=-$`6Z8ev*3jro7|kLO)7qmSH3PDtIYo?=U-p2DpGtgQk0(8fThXB5gkDi+I7# zqTv`*vyAznV@x8%gNDdfg;#naVY{S(OOHMAfhST=czRA!LpSw$WWJh>JFINChJ#)b zkwT9wQeM4MT-{*32_UQh+m;uqleg?#)}kKQDti43dp%|eH9{dW-ydWIV zTJC)o)|Q9c@IBLzxewGSl9djbwVd2E~3&d=XUrtI()wA|j_k?gCJwR2@5J zj0H`TXj{4Vro0*UdLg2x<8BglS;}D)sX!Ab00n)V`x?%JH2i4k1ESAr%K`{c@ez1%N zu&UAMDKT{*S|M^bgn>;BL>&8#MZ8a9(0%b)802C9o$zU==*m0x=c#4+g=aI zKvKzmceJr(jJr0i#v(x#RZW9K98bhlUdM#_#EzPvKSuN?BnXz-d=F$)q8uluj=^; z*uXHjO*mgnV5Q?O9V<`+#pEhmf+rjtjV)`*%EG;qfwB%DF@W$INztT8Iw%9IM_GSB zLSZm44grT=A2C(-0Rr9dA&I9o1|(XP>&DFi-H6Rq zH9SKpoH1^W4AkvjiQLJ?$lA5S;~<7qz*{+Ldvq`L{($&OWV8ysJq~_-SC~$U~Zna63}IUny4zQ zI|A!;t_lK<>B(o_%IB^QI6e|4yA;i)2knh;I)s`HE2LjhbFvIUpd8>qTHf1fChDaR ze0)iPUbv~*I?_^1oGvc&{BEqA(csso;?34vw7ju8=3-29G zlA^=Yy10ZTK35|!M3JH?56&WA?NR{OQ{h8SQ6b%R;{FlhsbQ`y$rfng{H#BXjW{`1 zC9mJjV##%!1Q8UpW{^Zjpg9~!1YRk{RY}3MSeMUMqOs4ZQBf}|j4E;svudBx_HN3t zN@}GxWqkP9b@k!`8uZFAMJ}D*D3BA}Nf`&s9-FNz#ST|ESogSgaI>qTqfv|zYxqU> zao@R{+lXOUKX1Q}e2cnt`J~KsBEo)KVj%$CT%m+{DqhAxP#e>yrptiT+o{#$-(ZJ} ztjRTRnf}juOJ0qB7QJ)6^Z5>s*$q{}__yK{LB-~+pDu2b@#|1iXOlWAs;0hY!^sxU znXKu!qf~($nOG%qlHw~tb9<&g2#e;;2(}31XjztC^f^W%l%QnpK~oH7DQsc`26pQa z0LHAyuF*&x(NoL_Pta$EXMqwOzgmYR?@vfTN(HB|PPA(H%wy)C!*ZIN%tMqYQ(~C> z3Hgj0ZtViEeL)W{vX41GIuKE~{MegV?CJP!$=7WZt|6}h3GM5p4n{ zM`p^F$0#tnjV<4|9|$HWXyXkjsCCD>m)eNf)wbm^ zyc0~H_5yMvbqp)<*(mPLIu=N}%HGg?p3sE?4fEe64vsdOsJQEBM=wPzjyU{Eu`%Hr zUStwnOasr!ryZtdCni!Y7I2St9jJ9y3X_Y}Y!wBPb?YQx|E_GXt84x%W9M~@3BD~% z)YH~EV7sk@BMy^L^yC1AV(Za&0$`9O)W+Qy0=eiFJc|a7i=9xRO5A|mS%9*6aK#Sw zRZ)b8YK;?#rdy!-9W1�!4#s_K?gS_^{x6Vwx}u&XP9oWq$eI_oKgo!X!Pn)oF07N4o^=9<{a<#vcC%(l+P}aU|0P$KIB=oU$%hf z(0mgOKJf2~9#E7?+T~mpIT&afM+u~<9t|XoO4PP(E5Oc$@K-9bSrztK-+mgVaZl+; z>>wIpU+8j5*j!p*)mVY2SG5+EtrN@n;%COqZz&&B?K2@tuyd z{Al!Xj*Gp#vq_&J9F-?PR2Sg~QJ5&6~hf#xfVXe^>etr~nBlh){~lin!Sf@a5S zL~Y7btkeM@js2Y|wQEJn16R$w{g5uRZT@VALHsSyWrxV+A*Aw!#v5e+j4gUAe$-cF zPh_gP{M^<1Fe11=%9)n^jtT-Ae~W)&4=!dc&3os)^S*H)b@RhE{GPL6W0^H$$=Zy` zBRmiyTjKCv`x6_>SEtn7lzL^#Dd+!}&9~{=gnfSz^OnfHM+WeJ^Zsu$Zz&TPUBX7d z)dzfC^hwZ2_eQEh`OblC!SDHbmHD;`3CC2x(rKb&ldU_}!j|Idp%knvjB+{28Lh_kcgiKNDhrC_5c$48Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg{baajJfNd}dx|NqoFsL1hUC&@h|a P{FKt1R6CHB#ULvHT$Co7 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/base/capabilities_pb.py b/google_appengine/google/appengine/base/capabilities_pb.py new file mode 100755 index 0000000..c0434ef --- /dev/null +++ b/google_appengine/google/appengine/base/capabilities_pb.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class CapabilityConfigList(ProtocolBuffer.ProtocolMessage): + has_default_config_ = 0 + default_config_ = None + + def __init__(self, contents=None): + self.config_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def config_size(self): return len(self.config_) + def config_list(self): return self.config_ + + def config(self, i): + return self.config_[i] + + def mutable_config(self, i): + return self.config_[i] + + def add_config(self): + x = CapabilityConfig() + self.config_.append(x) + return x + + def clear_config(self): + self.config_ = [] + def default_config(self): + if self.default_config_ is None: + self.lazy_init_lock_.acquire() + try: + if self.default_config_ is None: self.default_config_ = CapabilityConfig() + finally: + self.lazy_init_lock_.release() + return self.default_config_ + + def mutable_default_config(self): self.has_default_config_ = 1; return self.default_config() + + def clear_default_config(self): + if self.has_default_config_: + self.has_default_config_ = 0; + if self.default_config_ is not None: self.default_config_.Clear() + + def has_default_config(self): return self.has_default_config_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.config_size()): self.add_config().CopyFrom(x.config(i)) + if (x.has_default_config()): self.mutable_default_config().MergeFrom(x.default_config()) + + def Equals(self, x): + if x is self: return 1 + if len(self.config_) != len(x.config_): return 0 + for e1, e2 in zip(self.config_, x.config_): + if e1 != e2: return 0 + if self.has_default_config_ != x.has_default_config_: return 0 + if self.has_default_config_ and self.default_config_ != x.default_config_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.config_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_default_config_ and not self.default_config_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.config_) + for i in xrange(len(self.config_)): n += self.lengthString(self.config_[i].ByteSize()) + if (self.has_default_config_): n += 1 + self.lengthString(self.default_config_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_config() + self.clear_default_config() + + def OutputUnchecked(self, out): + for i in xrange(len(self.config_)): + out.putVarInt32(10) + out.putVarInt32(self.config_[i].ByteSize()) + self.config_[i].OutputUnchecked(out) + if (self.has_default_config_): + out.putVarInt32(18) + out.putVarInt32(self.default_config_.ByteSize()) + self.default_config_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_config().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_default_config().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.config_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("config%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_default_config_: + res+=prefix+"default_config <\n" + res+=self.default_config_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kconfig = 1 + kdefault_config = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "config", + 2: "default_config", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CapabilityConfig(ProtocolBuffer.ProtocolMessage): + + ENABLED = 1 + SCHEDULED = 2 + DISABLED = 3 + UNKNOWN = 4 + + _Status_NAMES = { + 1: "ENABLED", + 2: "SCHEDULED", + 3: "DISABLED", + 4: "UNKNOWN", + } + + def Status_Name(cls, x): return cls._Status_NAMES.get(x, "") + Status_Name = classmethod(Status_Name) + + has_package_ = 0 + package_ = "" + has_capability_ = 0 + capability_ = "" + has_status_ = 0 + status_ = 4 + has_scheduled_time_ = 0 + scheduled_time_ = "" + has_internal_message_ = 0 + internal_message_ = "" + has_admin_message_ = 0 + admin_message_ = "" + has_error_message_ = 0 + error_message_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def package(self): return self.package_ + + def set_package(self, x): + self.has_package_ = 1 + self.package_ = x + + def clear_package(self): + if self.has_package_: + self.has_package_ = 0 + self.package_ = "" + + def has_package(self): return self.has_package_ + + def capability(self): return self.capability_ + + def set_capability(self, x): + self.has_capability_ = 1 + self.capability_ = x + + def clear_capability(self): + if self.has_capability_: + self.has_capability_ = 0 + self.capability_ = "" + + def has_capability(self): return self.has_capability_ + + def status(self): return self.status_ + + def set_status(self, x): + self.has_status_ = 1 + self.status_ = x + + def clear_status(self): + if self.has_status_: + self.has_status_ = 0 + self.status_ = 4 + + def has_status(self): return self.has_status_ + + def scheduled_time(self): return self.scheduled_time_ + + def set_scheduled_time(self, x): + self.has_scheduled_time_ = 1 + self.scheduled_time_ = x + + def clear_scheduled_time(self): + if self.has_scheduled_time_: + self.has_scheduled_time_ = 0 + self.scheduled_time_ = "" + + def has_scheduled_time(self): return self.has_scheduled_time_ + + def internal_message(self): return self.internal_message_ + + def set_internal_message(self, x): + self.has_internal_message_ = 1 + self.internal_message_ = x + + def clear_internal_message(self): + if self.has_internal_message_: + self.has_internal_message_ = 0 + self.internal_message_ = "" + + def has_internal_message(self): return self.has_internal_message_ + + def admin_message(self): return self.admin_message_ + + def set_admin_message(self, x): + self.has_admin_message_ = 1 + self.admin_message_ = x + + def clear_admin_message(self): + if self.has_admin_message_: + self.has_admin_message_ = 0 + self.admin_message_ = "" + + def has_admin_message(self): return self.has_admin_message_ + + def error_message(self): return self.error_message_ + + def set_error_message(self, x): + self.has_error_message_ = 1 + self.error_message_ = x + + def clear_error_message(self): + if self.has_error_message_: + self.has_error_message_ = 0 + self.error_message_ = "" + + def has_error_message(self): return self.has_error_message_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_package()): self.set_package(x.package()) + if (x.has_capability()): self.set_capability(x.capability()) + if (x.has_status()): self.set_status(x.status()) + if (x.has_scheduled_time()): self.set_scheduled_time(x.scheduled_time()) + if (x.has_internal_message()): self.set_internal_message(x.internal_message()) + if (x.has_admin_message()): self.set_admin_message(x.admin_message()) + if (x.has_error_message()): self.set_error_message(x.error_message()) + + def Equals(self, x): + if x is self: return 1 + if self.has_package_ != x.has_package_: return 0 + if self.has_package_ and self.package_ != x.package_: return 0 + if self.has_capability_ != x.has_capability_: return 0 + if self.has_capability_ and self.capability_ != x.capability_: return 0 + if self.has_status_ != x.has_status_: return 0 + if self.has_status_ and self.status_ != x.status_: return 0 + if self.has_scheduled_time_ != x.has_scheduled_time_: return 0 + if self.has_scheduled_time_ and self.scheduled_time_ != x.scheduled_time_: return 0 + if self.has_internal_message_ != x.has_internal_message_: return 0 + if self.has_internal_message_ and self.internal_message_ != x.internal_message_: return 0 + if self.has_admin_message_ != x.has_admin_message_: return 0 + if self.has_admin_message_ and self.admin_message_ != x.admin_message_: return 0 + if self.has_error_message_ != x.has_error_message_: return 0 + if self.has_error_message_ and self.error_message_ != x.error_message_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_package_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: package not set.') + if (not self.has_capability_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: capability not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.package_)) + n += self.lengthString(len(self.capability_)) + if (self.has_status_): n += 1 + self.lengthVarInt64(self.status_) + if (self.has_scheduled_time_): n += 1 + self.lengthString(len(self.scheduled_time_)) + if (self.has_internal_message_): n += 1 + self.lengthString(len(self.internal_message_)) + if (self.has_admin_message_): n += 1 + self.lengthString(len(self.admin_message_)) + if (self.has_error_message_): n += 1 + self.lengthString(len(self.error_message_)) + return n + 2 + + def Clear(self): + self.clear_package() + self.clear_capability() + self.clear_status() + self.clear_scheduled_time() + self.clear_internal_message() + self.clear_admin_message() + self.clear_error_message() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.package_) + out.putVarInt32(18) + out.putPrefixedString(self.capability_) + if (self.has_status_): + out.putVarInt32(24) + out.putVarInt32(self.status_) + if (self.has_internal_message_): + out.putVarInt32(34) + out.putPrefixedString(self.internal_message_) + if (self.has_admin_message_): + out.putVarInt32(42) + out.putPrefixedString(self.admin_message_) + if (self.has_error_message_): + out.putVarInt32(50) + out.putPrefixedString(self.error_message_) + if (self.has_scheduled_time_): + out.putVarInt32(58) + out.putPrefixedString(self.scheduled_time_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_package(d.getPrefixedString()) + continue + if tt == 18: + self.set_capability(d.getPrefixedString()) + continue + if tt == 24: + self.set_status(d.getVarInt32()) + continue + if tt == 34: + self.set_internal_message(d.getPrefixedString()) + continue + if tt == 42: + self.set_admin_message(d.getPrefixedString()) + continue + if tt == 50: + self.set_error_message(d.getPrefixedString()) + continue + if tt == 58: + self.set_scheduled_time(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_package_: res+=prefix+("package: %s\n" % self.DebugFormatString(self.package_)) + if self.has_capability_: res+=prefix+("capability: %s\n" % self.DebugFormatString(self.capability_)) + if self.has_status_: res+=prefix+("status: %s\n" % self.DebugFormatInt32(self.status_)) + if self.has_scheduled_time_: res+=prefix+("scheduled_time: %s\n" % self.DebugFormatString(self.scheduled_time_)) + if self.has_internal_message_: res+=prefix+("internal_message: %s\n" % self.DebugFormatString(self.internal_message_)) + if self.has_admin_message_: res+=prefix+("admin_message: %s\n" % self.DebugFormatString(self.admin_message_)) + if self.has_error_message_: res+=prefix+("error_message: %s\n" % self.DebugFormatString(self.error_message_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kpackage = 1 + kcapability = 2 + kstatus = 3 + kscheduled_time = 7 + kinternal_message = 4 + kadmin_message = 5 + kerror_message = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "package", + 2: "capability", + 3: "status", + 4: "internal_message", + 5: "admin_message", + 6: "error_message", + 7: "scheduled_time", + }, 7) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + 7: ProtocolBuffer.Encoder.STRING, + }, 7, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['CapabilityConfigList','CapabilityConfig'] diff --git a/google_appengine/google/appengine/base/capabilities_pb.pyc b/google_appengine/google/appengine/base/capabilities_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cf873a3031f2f44f769de0b547739050bd8131c GIT binary patch literal 20418 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/cron/GrocLexer.py b/google_appengine/google/appengine/cron/GrocLexer.py new file mode 100755 index 0000000..03324c5 --- /dev/null +++ b/google_appengine/google/appengine/cron/GrocLexer.py @@ -0,0 +1,1697 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import sys +from antlr3 import * +from antlr3.compat import set, frozenset + + +HIDDEN = BaseRecognizer.HIDDEN + +THIRD=13 +SEPTEMBER=36 +FOURTH=14 +SECOND=12 +WEDNESDAY=22 +NOVEMBER=38 +SATURDAY=25 +JULY=34 +APRIL=31 +DIGITS=8 +OCTOBER=37 +MAY=32 +EVERY=6 +FEBRUARY=29 +MONDAY=20 +SUNDAY=26 +DAY=19 +JUNE=33 +OF=4 +MARCH=30 +EOF=-1 +JANUARY=28 +MONTH=27 +FRIDAY=24 +MINUTES=18 +FIFTH=15 +TIME=5 +SYNCHRONIZED=9 +WS=41 +QUARTER=40 +THURSDAY=23 +COMMA=10 +DECEMBER=39 +AUGUST=35 +DIGIT=7 +TUESDAY=21 +HOURS=17 +FOURTH_OR_FIFTH=16 +FIRST=11 + + +class GrocLexer(Lexer): + + grammarFileName = "Groc.g" + antlr_version = version_str_to_tuple("3.1.1") + antlr_version_str = "3.1.1" + + def __init__(self, input=None, state=None): + if state is None: + state = RecognizerSharedState() + Lexer.__init__(self, input, state) + + self.dfa25 = self.DFA25( + self, 25, + eot = self.DFA25_eot, + eof = self.DFA25_eof, + min = self.DFA25_min, + max = self.DFA25_max, + accept = self.DFA25_accept, + special = self.DFA25_special, + transition = self.DFA25_transition + ) + + + + + + + def mTIME(self, ): + + try: + _type = TIME + _channel = DEFAULT_CHANNEL + + pass + alt1 = 4 + LA1 = self.input.LA(1) + if LA1 == 48: + LA1_1 = self.input.LA(2) + + if ((48 <= LA1_1 <= 57)) : + alt1 = 2 + elif (LA1_1 == 58) : + alt1 = 1 + else: + nvae = NoViableAltException("", 1, 1, self.input) + + raise nvae + + elif LA1 == 49: + LA1_2 = self.input.LA(2) + + if (LA1_2 == 58) : + alt1 = 1 + elif ((48 <= LA1_2 <= 57)) : + alt1 = 3 + else: + nvae = NoViableAltException("", 1, 2, self.input) + + raise nvae + + elif LA1 == 50: + LA1_3 = self.input.LA(2) + + if ((48 <= LA1_3 <= 51)) : + alt1 = 4 + elif (LA1_3 == 58) : + alt1 = 1 + else: + nvae = NoViableAltException("", 1, 3, self.input) + + raise nvae + + elif LA1 == 51 or LA1 == 52 or LA1 == 53 or LA1 == 54 or LA1 == 55 or LA1 == 56 or LA1 == 57: + alt1 = 1 + else: + nvae = NoViableAltException("", 1, 0, self.input) + + raise nvae + + if alt1 == 1: + pass + self.mDIGIT() + + + elif alt1 == 2: + pass + pass + self.match(48) + self.mDIGIT() + + + + + + elif alt1 == 3: + pass + pass + self.match(49) + self.mDIGIT() + + + + + + elif alt1 == 4: + pass + pass + self.match(50) + self.matchRange(48, 51) + + + + + + + self.match(58) + pass + self.matchRange(48, 53) + self.mDIGIT() + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mSYNCHRONIZED(self, ): + + try: + _type = SYNCHRONIZED + _channel = DEFAULT_CHANNEL + + pass + self.match("synchronized") + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFIRST(self, ): + + try: + _type = FIRST + _channel = DEFAULT_CHANNEL + + pass + alt2 = 2 + LA2_0 = self.input.LA(1) + + if (LA2_0 == 49) : + alt2 = 1 + elif (LA2_0 == 102) : + alt2 = 2 + else: + nvae = NoViableAltException("", 2, 0, self.input) + + raise nvae + + if alt2 == 1: + pass + self.match("1st") + + + elif alt2 == 2: + pass + self.match("first") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mSECOND(self, ): + + try: + _type = SECOND + _channel = DEFAULT_CHANNEL + + pass + alt3 = 2 + LA3_0 = self.input.LA(1) + + if (LA3_0 == 50) : + alt3 = 1 + elif (LA3_0 == 115) : + alt3 = 2 + else: + nvae = NoViableAltException("", 3, 0, self.input) + + raise nvae + + if alt3 == 1: + pass + self.match("2nd") + + + elif alt3 == 2: + pass + self.match("second") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mTHIRD(self, ): + + try: + _type = THIRD + _channel = DEFAULT_CHANNEL + + pass + alt4 = 2 + LA4_0 = self.input.LA(1) + + if (LA4_0 == 51) : + alt4 = 1 + elif (LA4_0 == 116) : + alt4 = 2 + else: + nvae = NoViableAltException("", 4, 0, self.input) + + raise nvae + + if alt4 == 1: + pass + self.match("3rd") + + + elif alt4 == 2: + pass + self.match("third") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFOURTH(self, ): + + try: + _type = FOURTH + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("4th") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFIFTH(self, ): + + try: + _type = FIFTH + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("5th") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFOURTH_OR_FIFTH(self, ): + + try: + _type = FOURTH_OR_FIFTH + _channel = DEFAULT_CHANNEL + + pass + alt5 = 2 + LA5_0 = self.input.LA(1) + + if (LA5_0 == 102) : + LA5_1 = self.input.LA(2) + + if (LA5_1 == 111) : + alt5 = 1 + elif (LA5_1 == 105) : + alt5 = 2 + else: + nvae = NoViableAltException("", 5, 1, self.input) + + raise nvae + + else: + nvae = NoViableAltException("", 5, 0, self.input) + + raise nvae + + if alt5 == 1: + pass + pass + self.match("fourth") + _type = FOURTH; + + + + + + elif alt5 == 2: + pass + pass + self.match("fifth") + _type = FIFTH; + + + + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mDAY(self, ): + + try: + _type = DAY + _channel = DEFAULT_CHANNEL + + pass + self.match("day") + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mMONDAY(self, ): + + try: + _type = MONDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("mon") + alt6 = 2 + LA6_0 = self.input.LA(1) + + if (LA6_0 == 100) : + alt6 = 1 + if alt6 == 1: + pass + self.match("day") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mTUESDAY(self, ): + + try: + _type = TUESDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("tue") + alt7 = 2 + LA7_0 = self.input.LA(1) + + if (LA7_0 == 115) : + alt7 = 1 + if alt7 == 1: + pass + self.match("sday") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mWEDNESDAY(self, ): + + try: + _type = WEDNESDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("wed") + alt8 = 2 + LA8_0 = self.input.LA(1) + + if (LA8_0 == 110) : + alt8 = 1 + if alt8 == 1: + pass + self.match("nesday") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mTHURSDAY(self, ): + + try: + _type = THURSDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("thu") + alt9 = 2 + LA9_0 = self.input.LA(1) + + if (LA9_0 == 114) : + alt9 = 1 + if alt9 == 1: + pass + self.match("rsday") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFRIDAY(self, ): + + try: + _type = FRIDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("fri") + alt10 = 2 + LA10_0 = self.input.LA(1) + + if (LA10_0 == 100) : + alt10 = 1 + if alt10 == 1: + pass + self.match("day") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mSATURDAY(self, ): + + try: + _type = SATURDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("sat") + alt11 = 2 + LA11_0 = self.input.LA(1) + + if (LA11_0 == 117) : + alt11 = 1 + if alt11 == 1: + pass + self.match("urday") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mSUNDAY(self, ): + + try: + _type = SUNDAY + _channel = DEFAULT_CHANNEL + + pass + self.match("sun") + alt12 = 2 + LA12_0 = self.input.LA(1) + + if (LA12_0 == 100) : + alt12 = 1 + if alt12 == 1: + pass + self.match("day") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mJANUARY(self, ): + + try: + _type = JANUARY + _channel = DEFAULT_CHANNEL + + pass + self.match("jan") + alt13 = 2 + LA13_0 = self.input.LA(1) + + if (LA13_0 == 117) : + alt13 = 1 + if alt13 == 1: + pass + self.match("uary") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mFEBRUARY(self, ): + + try: + _type = FEBRUARY + _channel = DEFAULT_CHANNEL + + pass + self.match("feb") + alt14 = 2 + LA14_0 = self.input.LA(1) + + if (LA14_0 == 114) : + alt14 = 1 + if alt14 == 1: + pass + self.match("ruary") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mMARCH(self, ): + + try: + _type = MARCH + _channel = DEFAULT_CHANNEL + + pass + self.match("mar") + alt15 = 2 + LA15_0 = self.input.LA(1) + + if (LA15_0 == 99) : + alt15 = 1 + if alt15 == 1: + pass + self.match("ch") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mAPRIL(self, ): + + try: + _type = APRIL + _channel = DEFAULT_CHANNEL + + pass + self.match("apr") + alt16 = 2 + LA16_0 = self.input.LA(1) + + if (LA16_0 == 105) : + alt16 = 1 + if alt16 == 1: + pass + self.match("il") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mMAY(self, ): + + try: + _type = MAY + _channel = DEFAULT_CHANNEL + + pass + self.match("may") + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mJUNE(self, ): + + try: + _type = JUNE + _channel = DEFAULT_CHANNEL + + pass + self.match("jun") + alt17 = 2 + LA17_0 = self.input.LA(1) + + if (LA17_0 == 101) : + alt17 = 1 + if alt17 == 1: + pass + self.match(101) + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mJULY(self, ): + + try: + _type = JULY + _channel = DEFAULT_CHANNEL + + pass + self.match("jul") + alt18 = 2 + LA18_0 = self.input.LA(1) + + if (LA18_0 == 121) : + alt18 = 1 + if alt18 == 1: + pass + self.match(121) + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mAUGUST(self, ): + + try: + _type = AUGUST + _channel = DEFAULT_CHANNEL + + pass + self.match("aug") + alt19 = 2 + LA19_0 = self.input.LA(1) + + if (LA19_0 == 117) : + alt19 = 1 + if alt19 == 1: + pass + self.match("ust") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mSEPTEMBER(self, ): + + try: + _type = SEPTEMBER + _channel = DEFAULT_CHANNEL + + pass + self.match("sep") + alt20 = 2 + LA20_0 = self.input.LA(1) + + if (LA20_0 == 116) : + alt20 = 1 + if alt20 == 1: + pass + self.match("tember") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mOCTOBER(self, ): + + try: + _type = OCTOBER + _channel = DEFAULT_CHANNEL + + pass + self.match("oct") + alt21 = 2 + LA21_0 = self.input.LA(1) + + if (LA21_0 == 111) : + alt21 = 1 + if alt21 == 1: + pass + self.match("ober") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mNOVEMBER(self, ): + + try: + _type = NOVEMBER + _channel = DEFAULT_CHANNEL + + pass + self.match("nov") + alt22 = 2 + LA22_0 = self.input.LA(1) + + if (LA22_0 == 101) : + alt22 = 1 + if alt22 == 1: + pass + self.match("ember") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mDECEMBER(self, ): + + try: + _type = DECEMBER + _channel = DEFAULT_CHANNEL + + pass + self.match("dec") + alt23 = 2 + LA23_0 = self.input.LA(1) + + if (LA23_0 == 101) : + alt23 = 1 + if alt23 == 1: + pass + self.match("ember") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mMONTH(self, ): + + try: + _type = MONTH + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("month") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mQUARTER(self, ): + + try: + _type = QUARTER + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("quarter") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mEVERY(self, ): + + try: + _type = EVERY + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("every") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mHOURS(self, ): + + try: + _type = HOURS + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("hours") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mMINUTES(self, ): + + try: + _type = MINUTES + _channel = DEFAULT_CHANNEL + + pass + alt24 = 2 + LA24_0 = self.input.LA(1) + + if (LA24_0 == 109) : + LA24_1 = self.input.LA(2) + + if (LA24_1 == 105) : + LA24_2 = self.input.LA(3) + + if (LA24_2 == 110) : + LA24_3 = self.input.LA(4) + + if (LA24_3 == 115) : + alt24 = 1 + elif (LA24_3 == 117) : + alt24 = 2 + else: + nvae = NoViableAltException("", 24, 3, self.input) + + raise nvae + + else: + nvae = NoViableAltException("", 24, 2, self.input) + + raise nvae + + else: + nvae = NoViableAltException("", 24, 1, self.input) + + raise nvae + + else: + nvae = NoViableAltException("", 24, 0, self.input) + + raise nvae + + if alt24 == 1: + pass + self.match("mins") + + + elif alt24 == 2: + pass + self.match("minutes") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mCOMMA(self, ): + + try: + _type = COMMA + _channel = DEFAULT_CHANNEL + + pass + pass + self.match(44) + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mOF(self, ): + + try: + _type = OF + _channel = DEFAULT_CHANNEL + + pass + pass + self.match("of") + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mWS(self, ): + + try: + _type = WS + _channel = DEFAULT_CHANNEL + + pass + if (9 <= self.input.LA(1) <= 10) or self.input.LA(1) == 13 or self.input.LA(1) == 32: + self.input.consume() + else: + mse = MismatchedSetException(None, self.input) + self.recover(mse) + raise mse + + _channel=HIDDEN; + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mDIGIT(self, ): + + try: + _type = DIGIT + _channel = DEFAULT_CHANNEL + + pass + pass + self.matchRange(48, 57) + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mDIGITS(self, ): + + try: + _type = DIGITS + _channel = DEFAULT_CHANNEL + + pass + pass + self.mDIGIT() + self.mDIGIT() + + + + + + + self._state.type = _type + self._state.channel = _channel + + finally: + + pass + + + + + def mTokens(self): + alt25 = 38 + alt25 = self.dfa25.predict(self.input) + if alt25 == 1: + pass + self.mTIME() + + + elif alt25 == 2: + pass + self.mSYNCHRONIZED() + + + elif alt25 == 3: + pass + self.mFIRST() + + + elif alt25 == 4: + pass + self.mSECOND() + + + elif alt25 == 5: + pass + self.mTHIRD() + + + elif alt25 == 6: + pass + self.mFOURTH() + + + elif alt25 == 7: + pass + self.mFIFTH() + + + elif alt25 == 8: + pass + self.mFOURTH_OR_FIFTH() + + + elif alt25 == 9: + pass + self.mDAY() + + + elif alt25 == 10: + pass + self.mMONDAY() + + + elif alt25 == 11: + pass + self.mTUESDAY() + + + elif alt25 == 12: + pass + self.mWEDNESDAY() + + + elif alt25 == 13: + pass + self.mTHURSDAY() + + + elif alt25 == 14: + pass + self.mFRIDAY() + + + elif alt25 == 15: + pass + self.mSATURDAY() + + + elif alt25 == 16: + pass + self.mSUNDAY() + + + elif alt25 == 17: + pass + self.mJANUARY() + + + elif alt25 == 18: + pass + self.mFEBRUARY() + + + elif alt25 == 19: + pass + self.mMARCH() + + + elif alt25 == 20: + pass + self.mAPRIL() + + + elif alt25 == 21: + pass + self.mMAY() + + + elif alt25 == 22: + pass + self.mJUNE() + + + elif alt25 == 23: + pass + self.mJULY() + + + elif alt25 == 24: + pass + self.mAUGUST() + + + elif alt25 == 25: + pass + self.mSEPTEMBER() + + + elif alt25 == 26: + pass + self.mOCTOBER() + + + elif alt25 == 27: + pass + self.mNOVEMBER() + + + elif alt25 == 28: + pass + self.mDECEMBER() + + + elif alt25 == 29: + pass + self.mMONTH() + + + elif alt25 == 30: + pass + self.mQUARTER() + + + elif alt25 == 31: + pass + self.mEVERY() + + + elif alt25 == 32: + pass + self.mHOURS() + + + elif alt25 == 33: + pass + self.mMINUTES() + + + elif alt25 == 34: + pass + self.mCOMMA() + + + elif alt25 == 35: + pass + self.mOF() + + + elif alt25 == 36: + pass + self.mWS() + + + elif alt25 == 37: + pass + self.mDIGIT() + + + elif alt25 == 38: + pass + self.mDIGITS() + + + + + + + + + DFA25_eot = DFA.unpack( + u"\1\uffff\4\27\2\uffff\1\27\1\uffff\2\27\15\uffff\1\36\2\uffff\2" + u"\36\34\uffff\1\77\6\uffff" + ) + + DFA25_eof = DFA.unpack( + u"\100\uffff" + ) + + DFA25_min = DFA.unpack( + u"\1\11\4\60\1\141\1\145\1\60\1\150\2\60\2\141\1\uffff\1\141\1\160" + u"\1\143\7\uffff\1\72\2\uffff\2\72\4\uffff\1\143\2\uffff\1\146\4" + u"\uffff\1\151\4\uffff\1\156\1\162\2\uffff\1\154\6\uffff\1\164\6" + u"\uffff" + ) + + DFA25_max = DFA.unpack( + u"\1\167\1\72\1\163\1\156\1\162\1\171\1\162\1\164\1\165\1\164\1\72" + u"\1\145\1\157\1\uffff\2\165\1\146\7\uffff\1\72\2\uffff\2\72\4\uffff" + u"\1\160\2\uffff\1\162\4\uffff\1\165\4\uffff\1\156\1\171\2\uffff" + u"\1\156\6\uffff\1\164\6\uffff" + ) + + DFA25_accept = DFA.unpack( + u"\15\uffff\1\14\3\uffff\1\33\1\36\1\37\1\40\1\42\1\44\1\45\1\uffff" + u"\1\1\1\3\2\uffff\1\4\1\46\1\5\1\2\1\uffff\1\17\1\20\1\uffff\1\10" + u"\1\16\1\22\1\6\1\uffff\1\13\1\7\1\11\1\34\2\uffff\1\41\1\21\1\uffff" + u"\1\24\1\30\1\32\1\43\1\31\1\15\1\uffff\1\23\1\25\1\26\1\27\1\35" + u"\1\12" + ) + + DFA25_special = DFA.unpack( + u"\100\uffff" + ) + + + DFA25_transition = [ + DFA.unpack(u"\2\26\2\uffff\1\26\22\uffff\1\26\13\uffff\1\25\3\uffff" + u"\1\1\1\2\1\3\1\4\1\7\1\11\4\12\47\uffff\1\17\2\uffff\1\13\1\23" + u"\1\6\1\uffff\1\24\1\uffff\1\16\2\uffff\1\14\1\21\1\20\1\uffff\1" + u"\22\1\uffff\1\5\1\10\2\uffff\1\15"), + DFA.unpack(u"\12\30\1\31"), + DFA.unpack(u"\12\33\1\31\70\uffff\1\32"), + DFA.unpack(u"\4\34\6\36\1\31\63\uffff\1\35"), + DFA.unpack(u"\12\36\1\31\67\uffff\1\37"), + DFA.unpack(u"\1\42\3\uffff\1\41\17\uffff\1\43\3\uffff\1\40"), + DFA.unpack(u"\1\47\3\uffff\1\44\5\uffff\1\45\2\uffff\1\46"), + DFA.unpack(u"\12\36\1\31\71\uffff\1\50"), + DFA.unpack(u"\1\51\14\uffff\1\52"), + DFA.unpack(u"\12\36\1\31\71\uffff\1\53"), + DFA.unpack(u"\12\36\1\31"), + DFA.unpack(u"\1\54\3\uffff\1\55"), + DFA.unpack(u"\1\57\7\uffff\1\60\5\uffff\1\56"), + DFA.unpack(u""), + DFA.unpack(u"\1\61\23\uffff\1\62"), + DFA.unpack(u"\1\63\4\uffff\1\64"), + DFA.unpack(u"\1\65\2\uffff\1\66"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\31"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\31"), + DFA.unpack(u"\1\31"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\35\14\uffff\1\67"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\45\13\uffff\1\32"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\37\13\uffff\1\70"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\71"), + DFA.unpack(u"\1\72\6\uffff\1\73"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\75\1\uffff\1\74"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"\1\76"), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u""), + DFA.unpack(u"") + ] + + + DFA25 = DFA + + + + +def main(argv, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr): + from antlr3.main import LexerMain + main = LexerMain(GrocLexer) + main.stdin = stdin + main.stdout = stdout + main.stderr = stderr + main.execute(argv) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/google_appengine/google/appengine/cron/GrocLexer.pyc b/google_appengine/google/appengine/cron/GrocLexer.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17cbd37703fd5171c4ac5d36debe70e2f41b60fa GIT binary patch literal 22957 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 H;i&vSU=Z$V literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/cron/GrocParser.py b/google_appengine/google/appengine/cron/GrocParser.py new file mode 100755 index 0000000..d43dad9 --- /dev/null +++ b/google_appengine/google/appengine/cron/GrocParser.py @@ -0,0 +1,1137 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import sys +from antlr3 import * +from antlr3.compat import set, frozenset + + + + + +allOrdinals = set([1, 2, 3, 4, 5]) +numOrdinals = len(allOrdinals) + + + + +HIDDEN = BaseRecognizer.HIDDEN + +THIRD=13 +SEPTEMBER=36 +FOURTH=14 +SECOND=12 +WEDNESDAY=22 +NOVEMBER=38 +SATURDAY=25 +JULY=34 +APRIL=31 +DIGITS=8 +OCTOBER=37 +MAY=32 +EVERY=6 +FEBRUARY=29 +MONDAY=20 +SUNDAY=26 +JUNE=33 +DAY=19 +MARCH=30 +OF=4 +EOF=-1 +JANUARY=28 +MONTH=27 +FRIDAY=24 +FIFTH=15 +MINUTES=18 +TIME=5 +WS=41 +SYNCHRONIZED=9 +QUARTER=40 +THURSDAY=23 +COMMA=10 +DECEMBER=39 +AUGUST=35 +DIGIT=7 +TUESDAY=21 +HOURS=17 +FIRST=11 +FOURTH_OR_FIFTH=16 + +tokenNames = [ + "", "", "", "", + "OF", "TIME", "EVERY", "DIGIT", "DIGITS", "SYNCHRONIZED", "COMMA", "FIRST", + "SECOND", "THIRD", "FOURTH", "FIFTH", "FOURTH_OR_FIFTH", "HOURS", "MINUTES", + "DAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", + "SUNDAY", "MONTH", "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", + "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER", "QUARTER", + "WS" +] + + + + +class GrocParser(Parser): + grammarFileName = "Groc.g" + antlr_version = version_str_to_tuple("3.1.1") + antlr_version_str = "3.1.1" + tokenNames = tokenNames + + def __init__(self, input, state=None): + if state is None: + state = RecognizerSharedState() + + Parser.__init__(self, input, state) + + + self.dfa4 = self.DFA4( + self, 4, + eot = self.DFA4_eot, + eof = self.DFA4_eof, + min = self.DFA4_min, + max = self.DFA4_max, + accept = self.DFA4_accept, + special = self.DFA4_special, + transition = self.DFA4_transition + ) + + + + + self.ordinal_set = set() + self.weekday_set = set() + self.month_set = set() + self.monthday_set = set() + self.time_string = '' + self.interval_mins = 0 + self.period_string = '' + self.synchronized = False + + + + + + + + + + + valuesDict = { + SUNDAY: 0, + FIRST: 1, + MONDAY: 1, + JANUARY: 1, + TUESDAY: 2, + SECOND: 2, + FEBRUARY: 2, + WEDNESDAY: 3, + THIRD: 3, + MARCH: 3, + THURSDAY: 4, + FOURTH: 4, + APRIL: 4, + FRIDAY: 5, + FIFTH: 5, + MAY: 5, + SATURDAY: 6, + JUNE: 6, + JULY: 7, + AUGUST: 8, + SEPTEMBER: 9, + OCTOBER: 10, + NOVEMBER: 11, + DECEMBER: 12, + } + + def ValueOf(self, token_type): + return self.valuesDict.get(token_type, -1) + + + + + def timespec(self, ): + + try: + try: + pass + alt1 = 2 + LA1_0 = self.input.LA(1) + + if (LA1_0 == EVERY) : + LA1_1 = self.input.LA(2) + + if ((DIGIT <= LA1_1 <= DIGITS)) : + alt1 = 2 + elif ((DAY <= LA1_1 <= SUNDAY)) : + alt1 = 1 + else: + nvae = NoViableAltException("", 1, 1, self.input) + + raise nvae + + elif ((DIGIT <= LA1_0 <= DIGITS) or (FIRST <= LA1_0 <= FOURTH_OR_FIFTH)) : + alt1 = 1 + else: + nvae = NoViableAltException("", 1, 0, self.input) + + raise nvae + + if alt1 == 1: + pass + self._state.following.append(self.FOLLOW_specifictime_in_timespec44) + self.specifictime() + + self._state.following.pop() + + + elif alt1 == 2: + pass + self._state.following.append(self.FOLLOW_interval_in_timespec48) + self.interval() + + self._state.following.pop() + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def specifictime(self, ): + + TIME1 = None + + try: + try: + pass + pass + alt4 = 2 + alt4 = self.dfa4.predict(self.input) + if alt4 == 1: + pass + pass + alt2 = 2 + LA2_0 = self.input.LA(1) + + if (LA2_0 == EVERY or (FIRST <= LA2_0 <= FOURTH_OR_FIFTH)) : + alt2 = 1 + elif ((DIGIT <= LA2_0 <= DIGITS)) : + alt2 = 2 + else: + nvae = NoViableAltException("", 2, 0, self.input) + + raise nvae + + if alt2 == 1: + pass + pass + self._state.following.append(self.FOLLOW_ordinals_in_specifictime70) + self.ordinals() + + self._state.following.pop() + self._state.following.append(self.FOLLOW_weekdays_in_specifictime72) + self.weekdays() + + self._state.following.pop() + + + + + + elif alt2 == 2: + pass + self._state.following.append(self.FOLLOW_monthdays_in_specifictime75) + self.monthdays() + + self._state.following.pop() + + + + self.match(self.input, OF, self.FOLLOW_OF_in_specifictime78) + alt3 = 2 + LA3_0 = self.input.LA(1) + + if ((MONTH <= LA3_0 <= DECEMBER)) : + alt3 = 1 + elif ((FIRST <= LA3_0 <= THIRD) or LA3_0 == QUARTER) : + alt3 = 2 + else: + nvae = NoViableAltException("", 3, 0, self.input) + + raise nvae + + if alt3 == 1: + pass + self._state.following.append(self.FOLLOW_monthspec_in_specifictime81) + self.monthspec() + + self._state.following.pop() + + + elif alt3 == 2: + pass + self._state.following.append(self.FOLLOW_quarterspec_in_specifictime83) + self.quarterspec() + + self._state.following.pop() + + + + + + + + + elif alt4 == 2: + pass + pass + self._state.following.append(self.FOLLOW_ordinals_in_specifictime99) + self.ordinals() + + self._state.following.pop() + self._state.following.append(self.FOLLOW_weekdays_in_specifictime101) + self.weekdays() + + self._state.following.pop() + self.month_set = set(range(1,13)) + + + + + + + TIME1=self.match(self.input, TIME, self.FOLLOW_TIME_in_specifictime115) + self.time_string = TIME1.text + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def interval(self, ): + + intervalnum = None + period2 = None + + + try: + try: + pass + pass + self.match(self.input, EVERY, self.FOLLOW_EVERY_in_interval134) + intervalnum = self.input.LT(1) + if (DIGIT <= self.input.LA(1) <= DIGITS): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + self.interval_mins = int(intervalnum.text) + + self._state.following.append(self.FOLLOW_period_in_interval160) + period2 = self.period() + + self._state.following.pop() + + if ((period2 is not None) and [self.input.toString(period2.start,period2.stop)] or [None])[0] == "hours": + self.period_string = "hours" + else: + self.period_string = "minutes" + + alt5 = 2 + LA5_0 = self.input.LA(1) + + if (LA5_0 == SYNCHRONIZED) : + alt5 = 1 + if alt5 == 1: + pass + self.match(self.input, SYNCHRONIZED, self.FOLLOW_SYNCHRONIZED_in_interval171) + self.synchronized = True + + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def ordinals(self, ): + + try: + try: + pass + alt7 = 2 + LA7_0 = self.input.LA(1) + + if (LA7_0 == EVERY) : + alt7 = 1 + elif ((FIRST <= LA7_0 <= FOURTH_OR_FIFTH)) : + alt7 = 2 + else: + nvae = NoViableAltException("", 7, 0, self.input) + + raise nvae + + if alt7 == 1: + pass + self.match(self.input, EVERY, self.FOLLOW_EVERY_in_ordinals192) + self.ordinal_set = self.ordinal_set.union(allOrdinals) + + + elif alt7 == 2: + pass + pass + self._state.following.append(self.FOLLOW_ordinal_in_ordinals208) + self.ordinal() + + self._state.following.pop() + while True: + alt6 = 2 + LA6_0 = self.input.LA(1) + + if (LA6_0 == COMMA) : + alt6 = 1 + + + if alt6 == 1: + pass + self.match(self.input, COMMA, self.FOLLOW_COMMA_in_ordinals211) + self._state.following.append(self.FOLLOW_ordinal_in_ordinals213) + self.ordinal() + + self._state.following.pop() + + + else: + break + + + + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def ordinal(self, ): + + ord = None + + try: + try: + pass + ord = self.input.LT(1) + if (FIRST <= self.input.LA(1) <= FOURTH_OR_FIFTH): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + self.ordinal_set.add(self.ValueOf(ord.type)); + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + class period_return(ParserRuleReturnScope): + def __init__(self): + ParserRuleReturnScope.__init__(self) + + + + + + def period(self, ): + + retval = self.period_return() + retval.start = self.input.LT(1) + + try: + try: + pass + if (HOURS <= self.input.LA(1) <= MINUTES): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + + + retval.stop = self.input.LT(-1) + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return retval + + + + def monthdays(self, ): + + try: + try: + pass + pass + self._state.following.append(self.FOLLOW_monthday_in_monthdays296) + self.monthday() + + self._state.following.pop() + while True: + alt8 = 2 + LA8_0 = self.input.LA(1) + + if (LA8_0 == COMMA) : + alt8 = 1 + + + if alt8 == 1: + pass + self.match(self.input, COMMA, self.FOLLOW_COMMA_in_monthdays300) + self._state.following.append(self.FOLLOW_monthday_in_monthdays302) + self.monthday() + + self._state.following.pop() + + + else: + break + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def monthday(self, ): + + day = None + + try: + try: + pass + day = self.input.LT(1) + if (DIGIT <= self.input.LA(1) <= DIGITS): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + self.monthday_set.add(int(day.text)); + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def weekdays(self, ): + + try: + try: + pass + alt10 = 2 + LA10_0 = self.input.LA(1) + + if (LA10_0 == DAY) : + alt10 = 1 + elif ((MONDAY <= LA10_0 <= SUNDAY)) : + alt10 = 2 + else: + nvae = NoViableAltException("", 10, 0, self.input) + + raise nvae + + if alt10 == 1: + pass + self.match(self.input, DAY, self.FOLLOW_DAY_in_weekdays347) + + self.weekday_set = set([self.ValueOf(SUNDAY), self.ValueOf(MONDAY), + self.ValueOf(TUESDAY), self.ValueOf(WEDNESDAY), + self.ValueOf(THURSDAY), self.ValueOf(FRIDAY), + self.ValueOf(SATURDAY), self.ValueOf(SUNDAY)]) + + + + elif alt10 == 2: + pass + pass + self._state.following.append(self.FOLLOW_weekday_in_weekdays355) + self.weekday() + + self._state.following.pop() + while True: + alt9 = 2 + LA9_0 = self.input.LA(1) + + if (LA9_0 == COMMA) : + alt9 = 1 + + + if alt9 == 1: + pass + self.match(self.input, COMMA, self.FOLLOW_COMMA_in_weekdays358) + self._state.following.append(self.FOLLOW_weekday_in_weekdays360) + self.weekday() + + self._state.following.pop() + + + else: + break + + + + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def weekday(self, ): + + dayname = None + + try: + try: + pass + dayname = self.input.LT(1) + if (MONDAY <= self.input.LA(1) <= SUNDAY): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + self.weekday_set.add(self.ValueOf(dayname.type)) + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def monthspec(self, ): + + try: + try: + pass + alt11 = 2 + LA11_0 = self.input.LA(1) + + if (LA11_0 == MONTH) : + alt11 = 1 + elif ((JANUARY <= LA11_0 <= DECEMBER)) : + alt11 = 2 + else: + nvae = NoViableAltException("", 11, 0, self.input) + + raise nvae + + if alt11 == 1: + pass + self.match(self.input, MONTH, self.FOLLOW_MONTH_in_monthspec440) + + self.month_set = self.month_set.union(set([ + self.ValueOf(JANUARY), self.ValueOf(FEBRUARY), self.ValueOf(MARCH), + self.ValueOf(APRIL), self.ValueOf(MAY), self.ValueOf(JUNE), + self.ValueOf(JULY), self.ValueOf(AUGUST), self.ValueOf(SEPTEMBER), + self.ValueOf(OCTOBER), self.ValueOf(NOVEMBER), + self.ValueOf(DECEMBER)])) + + + + elif alt11 == 2: + pass + self._state.following.append(self.FOLLOW_months_in_monthspec450) + self.months() + + self._state.following.pop() + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def months(self, ): + + try: + try: + pass + pass + self._state.following.append(self.FOLLOW_month_in_months467) + self.month() + + self._state.following.pop() + while True: + alt12 = 2 + LA12_0 = self.input.LA(1) + + if (LA12_0 == COMMA) : + alt12 = 1 + + + if alt12 == 1: + pass + self.match(self.input, COMMA, self.FOLLOW_COMMA_in_months470) + self._state.following.append(self.FOLLOW_month_in_months472) + self.month() + + self._state.following.pop() + + + else: + break + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def month(self, ): + + monthname = None + + try: + try: + pass + monthname = self.input.LT(1) + if (JANUARY <= self.input.LA(1) <= DECEMBER): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + self.month_set.add(self.ValueOf(monthname.type)); + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def quarterspec(self, ): + + try: + try: + pass + alt13 = 2 + LA13_0 = self.input.LA(1) + + if (LA13_0 == QUARTER) : + alt13 = 1 + elif ((FIRST <= LA13_0 <= THIRD)) : + alt13 = 2 + else: + nvae = NoViableAltException("", 13, 0, self.input) + + raise nvae + + if alt13 == 1: + pass + self.match(self.input, QUARTER, self.FOLLOW_QUARTER_in_quarterspec564) + + self.month_set = self.month_set.union(set([ + self.ValueOf(JANUARY), self.ValueOf(APRIL), self.ValueOf(JULY), + self.ValueOf(OCTOBER)])) + + + elif alt13 == 2: + pass + pass + self._state.following.append(self.FOLLOW_quarter_ordinals_in_quarterspec576) + self.quarter_ordinals() + + self._state.following.pop() + self.match(self.input, MONTH, self.FOLLOW_MONTH_in_quarterspec578) + self.match(self.input, OF, self.FOLLOW_OF_in_quarterspec580) + self.match(self.input, QUARTER, self.FOLLOW_QUARTER_in_quarterspec582) + + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def quarter_ordinals(self, ): + + try: + try: + pass + pass + self._state.following.append(self.FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals601) + self.month_of_quarter_ordinal() + + self._state.following.pop() + while True: + alt14 = 2 + LA14_0 = self.input.LA(1) + + if (LA14_0 == COMMA) : + alt14 = 1 + + + if alt14 == 1: + pass + self.match(self.input, COMMA, self.FOLLOW_COMMA_in_quarter_ordinals604) + self._state.following.append(self.FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals606) + self.month_of_quarter_ordinal() + + self._state.following.pop() + + + else: + break + + + + + + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + def month_of_quarter_ordinal(self, ): + + offset = None + + try: + try: + pass + offset = self.input.LT(1) + if (FIRST <= self.input.LA(1) <= THIRD): + self.input.consume() + self._state.errorRecovery = False + + else: + mse = MismatchedSetException(None, self.input) + raise mse + + + + jOffset = self.ValueOf(offset.type) - 1 + self.month_set = self.month_set.union(set([ + jOffset + self.ValueOf(JANUARY), jOffset + self.ValueOf(APRIL), + jOffset + self.ValueOf(JULY), jOffset + self.ValueOf(OCTOBER)])) + + + + + except RecognitionException, re: + self.reportError(re) + self.recover(self.input, re) + finally: + + pass + + return + + + + + + + DFA4_eot = DFA.unpack( + u"\13\uffff" + ) + + DFA4_eof = DFA.unpack( + u"\13\uffff" + ) + + DFA4_min = DFA.unpack( + u"\1\6\1\23\1\12\1\uffff\2\4\1\13\1\uffff\1\24\1\12\1\4" + ) + + DFA4_max = DFA.unpack( + u"\1\20\2\32\1\uffff\1\5\1\12\1\20\1\uffff\2\32\1\12" + ) + + DFA4_accept = DFA.unpack( + u"\3\uffff\1\1\3\uffff\1\2\3\uffff" + ) + + DFA4_special = DFA.unpack( + u"\13\uffff" + ) + + + DFA4_transition = [ + DFA.unpack(u"\1\1\2\3\2\uffff\6\2"), + DFA.unpack(u"\1\4\7\5"), + DFA.unpack(u"\1\6\10\uffff\1\4\7\5"), + DFA.unpack(u""), + DFA.unpack(u"\1\3\1\7"), + DFA.unpack(u"\1\3\1\7\4\uffff\1\10"), + DFA.unpack(u"\6\11"), + DFA.unpack(u""), + DFA.unpack(u"\7\12"), + DFA.unpack(u"\1\6\10\uffff\1\4\7\5"), + DFA.unpack(u"\1\3\1\7\4\uffff\1\10") + ] + + + DFA4 = DFA + + + FOLLOW_specifictime_in_timespec44 = frozenset([1]) + FOLLOW_interval_in_timespec48 = frozenset([1]) + FOLLOW_ordinals_in_specifictime70 = frozenset([19, 20, 21, 22, 23, 24, 25, 26]) + FOLLOW_weekdays_in_specifictime72 = frozenset([4]) + FOLLOW_monthdays_in_specifictime75 = frozenset([4]) + FOLLOW_OF_in_specifictime78 = frozenset([11, 12, 13, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]) + FOLLOW_monthspec_in_specifictime81 = frozenset([5]) + FOLLOW_quarterspec_in_specifictime83 = frozenset([5]) + FOLLOW_ordinals_in_specifictime99 = frozenset([19, 20, 21, 22, 23, 24, 25, 26]) + FOLLOW_weekdays_in_specifictime101 = frozenset([5]) + FOLLOW_TIME_in_specifictime115 = frozenset([1]) + FOLLOW_EVERY_in_interval134 = frozenset([7, 8]) + FOLLOW_set_in_interval144 = frozenset([17, 18]) + FOLLOW_period_in_interval160 = frozenset([1, 9]) + FOLLOW_SYNCHRONIZED_in_interval171 = frozenset([1]) + FOLLOW_EVERY_in_ordinals192 = frozenset([1]) + FOLLOW_ordinal_in_ordinals208 = frozenset([1, 10]) + FOLLOW_COMMA_in_ordinals211 = frozenset([11, 12, 13, 14, 15, 16]) + FOLLOW_ordinal_in_ordinals213 = frozenset([1, 10]) + FOLLOW_set_in_ordinal234 = frozenset([1]) + FOLLOW_set_in_period273 = frozenset([1]) + FOLLOW_monthday_in_monthdays296 = frozenset([1, 10]) + FOLLOW_COMMA_in_monthdays300 = frozenset([7, 8]) + FOLLOW_monthday_in_monthdays302 = frozenset([1, 10]) + FOLLOW_set_in_monthday322 = frozenset([1]) + FOLLOW_DAY_in_weekdays347 = frozenset([1]) + FOLLOW_weekday_in_weekdays355 = frozenset([1, 10]) + FOLLOW_COMMA_in_weekdays358 = frozenset([19, 20, 21, 22, 23, 24, 25, 26]) + FOLLOW_weekday_in_weekdays360 = frozenset([1, 10]) + FOLLOW_set_in_weekday381 = frozenset([1]) + FOLLOW_MONTH_in_monthspec440 = frozenset([1]) + FOLLOW_months_in_monthspec450 = frozenset([1]) + FOLLOW_month_in_months467 = frozenset([1, 10]) + FOLLOW_COMMA_in_months470 = frozenset([27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]) + FOLLOW_month_in_months472 = frozenset([1, 10]) + FOLLOW_set_in_month491 = frozenset([1]) + FOLLOW_QUARTER_in_quarterspec564 = frozenset([1]) + FOLLOW_quarter_ordinals_in_quarterspec576 = frozenset([27]) + FOLLOW_MONTH_in_quarterspec578 = frozenset([4]) + FOLLOW_OF_in_quarterspec580 = frozenset([40]) + FOLLOW_QUARTER_in_quarterspec582 = frozenset([1]) + FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals601 = frozenset([1, 10]) + FOLLOW_COMMA_in_quarter_ordinals604 = frozenset([11, 12, 13, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40]) + FOLLOW_month_of_quarter_ordinal_in_quarter_ordinals606 = frozenset([1, 10]) + FOLLOW_set_in_month_of_quarter_ordinal625 = frozenset([1]) + + + +def main(argv, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr): + from antlr3.main import ParserMain + main = ParserMain("GrocLexer", GrocParser) + main.stdin = stdin + main.stdout = stdout + main.stderr = stderr + main.execute(argv) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/google_appengine/google/appengine/cron/GrocParser.pyc b/google_appengine/google/appengine/cron/GrocParser.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ef5fa1f8fa20d38614c1243670b442f0232695b GIT binary patch literal 20254 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 jPG`KwXsp43`}goK+Gunc9sCEGh5@m@V*D{ z;Jx>ES$)>#tc&qV)`1l(hAE00vn;OlEN2@=YHbvE>rk+JM1w{o620nB{C`RWr90K& zrOst(&Hyl^0qVRkA)Xkrr5>f%gCcpPr+1`Glnqhi5L5{08|!fl~sY2kQ;!+k=L0}N0` zkjJne!62H?V;IEqc>;rEK5xLF0eJ#y2}ZcnfR6wl0NxCmkS~Esph#HJTqs(Qw?H+Z zXs}{=p;(4|g%*PqLf-AR$*FrNw#0#z)2XsTS~JtR@DnA*N-Hb9%EfbQGBK6b(E-$! z>QLEU@g1KXzlQhez_Y_lO?_eXBJK#sV={3Og#wNhD9UuVOFDO-OlzU6HCC7`n^}w2 z4kksZ*j%BSz^C^GkFLazUo5G*y7C85j^mb)7=W8PtIiab|ZFeYY0dSPYZloM*6aM#P| z(tJE2fXkF0fa3^0MDU(TRzrV9krD!y@)Sc9BTjY5@<XlS?SlQa@j;^V=GyfN8;_wc`?O$c_*Z(lyh>d5S1~U&k>{s+_K|YrSRM) zFNLVwc$2XMH|o2rWZxGDe_`IHkmZ3qay?e=Fv++`9Jy~$_^KRvA`edx{!P|sLlV;S6{Z$AoeZ?R2nP|EWztc2; z{^#i#c$zMeXu|pMfyhSx-G4?%`e|>#PlU|KI>o#o#U?N*iU^K@cySVek86&c#H>o- zIOb(S-}Q0AbDT7w?k(2{%qr|(R8;O@ft$T9+47WMseNfTd=>g zu$zzS6adLI^0UD42W0(3p6GxjEmUW85#eA@ z#FwJghz7TQE;joT@X{4plUOt{?y9Fh3sx>@%#`IkaYoV(oF8qt*FOl|{UP;P{&O^w Q)fnGsHM!Qf(Yew20ZU$DH2?qr literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/cron/groctimespecification.py b/google_appengine/google/appengine/cron/groctimespecification.py new file mode 100755 index 0000000..97d1caa --- /dev/null +++ b/google_appengine/google/appengine/cron/groctimespecification.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +"""Implementation of scheduling for Groc format schedules. + +A Groc schedule looks like '1st,2nd monday 9:00', or 'every 20 mins'. This +module takes a parsed schedule (produced by Antlr) and creates objects that +can produce times that match this schedule. + +A parsed schedule is one of two types - an Interval or a Specific Time. +See the class docstrings for more. + +Extensions to be considered: + + allowing a comma separated list of times to run + allowing the user to specify particular days of the month to run +""" + + +import calendar +import datetime + +try: + import pytz +except ImportError: + pytz = None + +import groc + +HOURS = 'hours' +MINUTES = 'minutes' + +try: + from pytz import NonExistentTimeError + from pytz import AmbiguousTimeError +except ImportError: + + class NonExistentTimeError(Exception): + pass + + class AmbiguousTimeError(Exception): + pass + + +def GrocTimeSpecification(schedule, timezone=None): + """Factory function. + + Turns a schedule specification into a TimeSpecification. + + Arguments: + schedule: the schedule specification, as a string + timezone: the optional timezone as a string for this specification. + Defaults to 'UTC' - valid entries are things like 'Australia/Victoria' + or 'PST8PDT'. + Returns: + a TimeSpecification instance + """ + parser = groc.CreateParser(schedule) + parser.timespec() + + if parser.getTokenStream().LT(1).getText(): + raise groc.GrocException( + 'Extra token %r' % parser.getTokenStream().LT(1).getText()) + + if parser.period_string: + return IntervalTimeSpecification(parser.interval_mins, + parser.period_string, + parser.synchronized) + else: + return SpecificTimeSpecification(parser.ordinal_set, parser.weekday_set, + parser.month_set, + parser.monthday_set, + parser.time_string, + timezone) + + +class TimeSpecification(object): + """Base class for time specifications.""" + + def GetMatches(self, start, n): + """Returns the next n times that match the schedule, starting at time start. + + Arguments: + start: a datetime to start from. Matches will start from after this time. + n: the number of matching times to return + + Returns: + a list of n datetime objects + """ + out = [] + for _ in range(n): + start = self.GetMatch(start) + out.append(start) + return out + + def GetMatch(self, start): + """Returns the next match after time start. + + Must be implemented in subclasses. + + Arguments: + start: a datetime to start with. Matches will start from this time. + + Returns: + a datetime object + """ + raise NotImplementedError + + +class IntervalTimeSpecification(TimeSpecification): + """A time specification for a given interval. + + An Interval type spec runs at the given fixed interval. It has three + attributes: + period - the type of interval, either 'hours' or 'minutes' + interval - the number of units of type period. + synchronized - whether to synchronize the times to be locked to a fixed + period (midnight). + """ + + def __init__(self, interval, period, synchronized=False): + super(IntervalTimeSpecification, self).__init__() + if interval < 1: + raise groc.GrocException('interval must be greater than zero') + self.interval = interval + self.period = period + self.synchronized = synchronized + if self.period == HOURS: + self.seconds = self.interval * 3600 + else: + self.seconds = self.interval * 60 + if self.synchronized: + if (self.seconds > 86400) or ((86400 % self.seconds) != 0): + raise groc.GrocException('can only use synchronized for periods that' + ' divide evenly into 24 hours') + + def GetMatch(self, t): + """Returns the next match after time 't'. + + Arguments: + t: a datetime to start from. Matches will start from after this time. + + Returns: + a datetime object + """ + if not self.synchronized: + return t + datetime.timedelta(seconds=self.seconds) + else: + daystart = t.replace(hour=0, minute=0, second=0, microsecond=0) + dayseconds = (t - daystart).seconds + delta = self.seconds - (dayseconds % self.seconds) + return t + datetime.timedelta(seconds=delta) + + +class SpecificTimeSpecification(TimeSpecification): + """Specific time specification. + + A Specific interval is more complex, but defines a certain time to run and + the days that it should run. It has the following attributes: + time - the time of day to run, as 'HH:MM' + ordinals - first, second, third &c, as a set of integers in 1..5 + months - the months that this should run, as a set of integers in 1..12 + weekdays - the days of the week that this should run, as a set of integers, + 0=Sunday, 6=Saturday + timezone - the optional timezone as a string for this specification. + Defaults to UTC - valid entries are things like Australia/Victoria + or PST8PDT. + + A specific time schedule can be quite complex. A schedule could look like + this: + '1st,third sat,sun of jan,feb,mar 09:15' + + In this case, ordinals would be {1,3}, weekdays {0,6}, months {1,2,3} and + time would be '09:15'. + """ + + timezone = None + + def __init__(self, ordinals=None, weekdays=None, months=None, monthdays=None, + timestr='00:00', timezone=None): + super(SpecificTimeSpecification, self).__init__() + if weekdays and monthdays: + raise ValueError('cannot supply both monthdays and weekdays') + if ordinals is None: + self.ordinals = set(range(1, 6)) + else: + self.ordinals = set(ordinals) + + if weekdays is None: + self.weekdays = set(range(7)) + else: + self.weekdays = set(weekdays) + + if months is None: + self.months = set(range(1, 13)) + else: + self.months = set(months) + + if not monthdays: + self.monthdays = set() + else: + if max(monthdays) > 31 or min(monthdays) < 1: + raise ValueError('invalid day of month') + self.monthdays = set(monthdays) + hourstr, minutestr = timestr.split(':') + self.time = datetime.time(int(hourstr), int(minutestr)) + if timezone: + if pytz is None: + raise ValueError('need pytz in order to specify a timezone') + self.timezone = pytz.timezone(timezone) + + def _MatchingDays(self, year, month): + """Returns matching days for the given year and month. + + For the given year and month, return the days that match this instance's + day specification, based on either (a) the ordinals and weekdays, or + (b) the explicitly specified monthdays. If monthdays are specified, + dates that fall outside the range of the month will not be returned. + + Arguments: + year: the year as an integer + month: the month as an integer, in range 1-12 + + Returns: + a list of matching days, as ints in range 1-31 + """ + start_day, last_day = calendar.monthrange(year, month) + if self.monthdays: + return sorted([day for day in self.monthdays if day <= last_day]) + + out_days = [] + start_day = (start_day + 1) % 7 + for ordinal in self.ordinals: + for weekday in self.weekdays: + day = ((weekday - start_day) % 7) + 1 + day += 7 * (ordinal - 1) + if day <= last_day: + out_days.append(day) + return sorted(out_days) + + def _NextMonthGenerator(self, start, matches): + """Creates a generator that produces results from the set 'matches'. + + Matches must be >= 'start'. If none match, the wrap counter is incremented, + and the result set is reset to the full set. Yields a 2-tuple of (match, + wrapcount). + + Arguments: + start: first set of matches will be >= this value (an int) + matches: the set of potential matches (a sequence of ints) + + Yields: + a two-tuple of (match, wrap counter). match is an int in range (1-12), + wrapcount is a int indicating how many times we've wrapped around. + """ + potential = matches = sorted(matches) + after = start - 1 + wrapcount = 0 + while True: + potential = [x for x in potential if x > after] + if not potential: + wrapcount += 1 + potential = matches + after = potential[0] + yield (after, wrapcount) + + def GetMatch(self, start): + """Returns the next time that matches the schedule after time start. + + Arguments: + start: a UTC datetime to start from. Matches will start after this time + + Returns: + a datetime object + """ + start_time = start + if self.timezone and pytz is not None: + if not start_time.tzinfo: + start_time = pytz.utc.localize(start_time) + start_time = start_time.astimezone(self.timezone) + start_time = start_time.replace(tzinfo=None) + if self.months: + months = self._NextMonthGenerator(start_time.month, self.months) + while True: + month, yearwraps = months.next() + candidate_month = start_time.replace(day=1, month=month, + year=start_time.year + yearwraps) + + day_matches = self._MatchingDays(candidate_month.year, month) + + if ((candidate_month.year, candidate_month.month) + == (start_time.year, start_time.month)): + day_matches = [x for x in day_matches if x >= start_time.day] + while (day_matches and day_matches[0] == start_time.day + and start_time.time() >= self.time): + day_matches.pop(0) + while day_matches: + out = candidate_month.replace(day=day_matches[0], hour=self.time.hour, + minute=self.time.minute, second=0, + microsecond=0) + if self.timezone and pytz is not None: + try: + out = self.timezone.localize(out, is_dst=None) + except AmbiguousTimeError: + out = self.timezone.localize(out) + except NonExistentTimeError: + for _ in range(24): + out = out + datetime.timedelta(minutes=60) + try: + out = self.timezone.localize(out) + except NonExistentTimeError: + continue + break + out = out.astimezone(pytz.utc) + return out diff --git a/google_appengine/google/appengine/cron/groctimespecification.pyc b/google_appengine/google/appengine/cron/groctimespecification.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22144ce50b85ed96273c0ad304df32c64cea4324 GIT binary patch literal 11567 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/__init__.py b/google_appengine/google/appengine/datastore/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/datastore/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/datastore/__init__.pyc b/google_appengine/google/appengine/datastore/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8f446a08ffe7e76732693ddbc6856a8498c1273 GIT binary patch literal 164 zcwW2siI?lBOo~r30~9a%knvjB+{28Lh_kcgiKNDhrC_5~998Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg{DSNn&wHeo?A^e0*kJW=VX!UO{CE U2hc>D-29Z%oK!oIy~QAV06OR>xc~qF literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/action_pb.py b/google_appengine/google/appengine/datastore/action_pb.py new file mode 100755 index 0000000..dd97c6a --- /dev/null +++ b/google_appengine/google/appengine/datastore/action_pb.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class Action(ProtocolBuffer.ProtocolMessage): + pass diff --git a/google_appengine/google/appengine/datastore/action_pb.pyc b/google_appengine/google/appengine/datastore/action_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae3a3a7c6a1899d5a9812bf79e1a07603d7ac06 GIT binary patch literal 619 zcwV)VzfQw25XLX*AE8QxiiL%tV+LrDSOGx=8&s&B=n^^cHI3@nk?jaN!vn&r@B(m` zRt1TfQ~W7+vCqHzl#kDT@BLWbXRwVnt!rBDoJ7(SzzRSGFb^P*A{?+yP(t!sx;i;^~1w$T1gVL7SG6zz$`w9XUft>Z4P!mJR=Gto`v3!`^a zGTNA@+#^BY;ij{QzeFKZb2&-)k|goHCMk`q6{VY9J8=_|cxg(Eb!LT$Z_Tq-Mo1Uu z#^iKDR2AyH(6}wcKLshgaNby|HGD~xj;i&P>5jNJvvh+=)M2Ip$)>|dqaRgl>XZ?D pzX7b)WMH;JOUMVmTz^34M2=0@|A%j^yV#g@b;x({A2n!o!*2!(q3Zwu literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/datastore_index.py b/google_appengine/google/appengine/datastore/datastore_index.py new file mode 100755 index 0000000..0ea0bfa --- /dev/null +++ b/google_appengine/google/appengine/datastore/datastore_index.py @@ -0,0 +1,531 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Primitives for dealing with datastore indexes. + +Example index.yaml file: +------------------------ + +indexes: + +- kind: Cat + ancestor: no + properties: + - name: name + - name: age + direction: desc + +- kind: Cat + properties: + - name: name + direction: ascending + - name: whiskers + direction: descending + +- kind: Store + ancestor: yes + properties: + - name: business + direction: asc + - name: owner + direction: asc +""" + + + + + +from google.appengine.api import datastore_types +from google.appengine.api import validation +from google.appengine.api import yaml_errors +from google.appengine.api import yaml_object +from google.appengine.datastore import datastore_pb +from google.appengine.datastore import entity_pb + + + +class Property(validation.Validated): + """Representation for an individual property of an index. + + Attributes: + name: Name of attribute to sort by. + direction: Direction of sort. + """ + + ATTRIBUTES = { + 'name': validation.TYPE_STR, + 'direction': validation.Options(('asc', ('ascending',)), + ('desc', ('descending',)), + default='asc'), + } + + +class Index(validation.Validated): + """Individual index definition. + + Order of the properties properties determins a given indixes sort priority. + + Attributes: + kind: Datastore kind that index belongs to. + ancestors: Include ancestors in index. + properties: Properties to sort on. + """ + + ATTRIBUTES = { + 'kind': validation.TYPE_STR, + 'ancestor': validation.Type(bool, default=False), + 'properties': validation.Optional(validation.Repeated(Property)), + } + + +class IndexDefinitions(validation.Validated): + """Top level for index definition file. + + Attributes: + indexes: List of Index definitions. + """ + + ATTRIBUTES = { + 'indexes': validation.Optional(validation.Repeated(Index)), + } + + +def ParseIndexDefinitions(document): + """Parse an individual index definitions document from string or stream. + + Args: + document: Yaml document as a string or file-like stream. + + Raises: + EmptyConfigurationFile when the configuration file is empty. + MultipleConfigurationFile when the configuration file contains more than + one document. + + Returns: + Single parsed yaml file if one is defined, else None. + """ + try: + return yaml_object.BuildSingleObject(IndexDefinitions, document) + except yaml_errors.EmptyConfigurationFile: + return None + + +def ParseMultipleIndexDefinitions(document): + """Parse multiple index definitions documents from a string or stream. + + Args: + document: Yaml document as a string or file-like stream. + + Returns: + A list of datstore_index.IndexDefinitions objects, one for each document. + """ + return yaml_object.BuildObjects(IndexDefinitions, document) + + +def IndexDefinitionsToKeys(indexes): + """Convert IndexDefinitions to set of keys. + + Args: + indexes: A datastore_index.IndexDefinitions instance, or None. + + Returns: + A set of keys constructed from the argument, each key being a + tuple of the form (kind, ancestor, properties) where properties is + a tuple of (name, direction) pairs, direction being ASCENDING or + DESCENDING (the enums). + """ + keyset = set() + if indexes is not None: + if indexes.indexes: + for index in indexes.indexes: + keyset.add(IndexToKey(index)) + return keyset + + +def IndexToKey(index): + """Convert Index to key. + + Args: + index: A datastore_index.Index instance (not None!). + + Returns: + A tuple of the form (kind, ancestor, properties) where properties + is a tuple of (name, direction) pairs, direction being ASCENDING + or DESCENDING (the enums). + """ + props = [] + if index.properties is not None: + for prop in index.properties: + if prop.direction == 'asc': + direction = ASCENDING + else: + direction = DESCENDING + props.append((prop.name, direction)) + return index.kind, index.ancestor, tuple(props) + + + + +ASCENDING = datastore_pb.Query_Order.ASCENDING +DESCENDING = datastore_pb.Query_Order.DESCENDING + +EQUALITY_OPERATORS = set((datastore_pb.Query_Filter.EQUAL, + )) +INEQUALITY_OPERATORS = set((datastore_pb.Query_Filter.LESS_THAN, + datastore_pb.Query_Filter.LESS_THAN_OR_EQUAL, + datastore_pb.Query_Filter.GREATER_THAN, + datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL, + )) +EXISTS_OPERATORS = set((datastore_pb.Query_Filter.EXISTS, + )) + +_DIRECTION_MAP = { + 'asc': entity_pb.Index_Property.ASCENDING, + 'ascending': entity_pb.Index_Property.ASCENDING, + 'desc': entity_pb.Index_Property.DESCENDING, + 'descending': entity_pb.Index_Property.DESCENDING, + } + +def Normalize(filters, orders): + """ Normalizes filter and order query components. + + The resulting components have the same effect as the given components if used + in a query. + + Returns: + (filter, orders) the reduced set of filters and orders + """ + + eq_properties = set() + inequality_properties = set() + + for f in filters: + if f.op() == datastore_pb.Query_Filter.IN and f.property_size() == 1: + f.set_op(datastore_pb.Query_Filter.EQUAL) + if f.op() in EQUALITY_OPERATORS: + eq_properties.add(f.property(0).name()) + elif f.op() in INEQUALITY_OPERATORS: + inequality_properties.add(f.property(0).name()) + + eq_properties -= inequality_properties + + remove_set = eq_properties.copy() + new_orders = [] + for o in orders: + if o.property() not in remove_set: + remove_set.add(o.property()) + new_orders.append(o) + orders = new_orders + + + if datastore_types._KEY_SPECIAL_PROPERTY in eq_properties: + orders = [] + + new_orders = [] + for o in orders: + if o.property() == datastore_types._KEY_SPECIAL_PROPERTY: + new_orders.append(o) + break + new_orders.append(o) + orders = new_orders + + return (filters, orders) + + +def RemoveNativelySupportedComponents(filters, orders): + """ Removes query components that are natively supported by the datastore. + + The resulting filters and orders should not be used in an actual query. + + Returns + (filters, orders) the reduced set of filters and orders + """ + (filters, orders) = Normalize(filters, orders) + + has_key_desc_order = False + if orders and orders[-1].property() == datastore_types._KEY_SPECIAL_PROPERTY: + if orders[-1].direction() == ASCENDING: + orders = orders[:-1] + else: + has_key_desc_order = True + + if not has_key_desc_order: + for f in filters: + if (f.op() in INEQUALITY_OPERATORS and + f.property(0).name() != datastore_types._KEY_SPECIAL_PROPERTY): + break + else: + filters = [f for f in filters + if f.property(0).name() != datastore_types._KEY_SPECIAL_PROPERTY] + + return (filters, orders) + + +def CompositeIndexForQuery(query): + """Return the composite index needed for a query. + + A query is translated into a tuple, as follows: + + - The first item is the kind string, or None if we're not filtering + on kind (see below). + + - The second item is a bool giving whether the query specifies an + ancestor. + + - After that come (property, ASCENDING) pairs for those Filter + entries whose operator is EQUAL or IN. Since the order of these + doesn't matter, they are sorted by property name to normalize them + in order to avoid duplicates. + + - After that comes at most one (property, ASCENDING) pair for a + Filter entry whose operator is on of the four inequalities. There + can be at most one of these. + + - After that come all the (property, direction) pairs for the Order + entries, in the order given in the query. Exceptions: + (a) if there is a Filter entry with an inequality operator that matches + the first Order entry, the first order pair is omitted (or, + equivalently, in this case the inequality pair is omitted). + (b) if an Order entry corresponds to an equality filter, it is ignored + (since there will only ever be one value returned). + (c) if there is an equality filter on __key__ all orders are dropped + (since there will be at most one result returned). + (d) if there is an order on __key__ all further orders are dropped (since + keys are unique). + (e) orders on __key__ ASCENDING are dropped (since this is supported + natively by the datastore). + + - Finally, if there are Filter entries whose operator is EXISTS, and + whose property names are not already listed, they are added, with + the direction set to ASCENDING. + + This algorithm should consume all Filter and Order entries. + + Additional notes: + + - The low-level implementation allows queries that don't specify a + kind; but the Python API doesn't support this yet. + + - If there's an inequality filter and one or more sort orders, the + first sort order *must* match the inequality filter. + + - The following indexes are always built in and should be suppressed: + - query on kind only; + - query on kind and one filter *or* one order; + - query on ancestor only, without kind (not exposed in Python yet); + - query on kind and equality filters only, no order (with or without + ancestor). + + - While the protocol buffer allows a Filter to contain multiple + properties, we don't use this. It is only needed for the IN operator + but this is (currently) handled on the client side, so in practice + each Filter is expected to have exactly one property. + + Args: + query: A datastore_pb.Query instance. + + Returns: + A tuple of the form (required, kind, ancestor, (prop1, prop2, ...), neq): + required: boolean, whether the index is required + kind: the kind or None; + ancestor: True if this is an ancestor query; + prop1, prop2, ...: tuples of the form (name, direction) where: + name: a property name; + direction: datastore_pb.Query_Order.ASCENDING or ...DESCENDING; + neq: the number of prop tuples corresponding to equality filters. + """ + required = True + + kind = query.kind() + ancestor = query.has_ancestor() + filters = query.filter_list() + orders = query.order_list() + + for filter in filters: + assert filter.op() != datastore_pb.Query_Filter.IN, 'Filter.op()==IN' + nprops = len(filter.property_list()) + assert nprops == 1, 'Filter has %s properties, expected 1' % nprops + + if not kind: + required = False + + (filters, orders) = RemoveNativelySupportedComponents(filters, orders) + + eq_filters = [f for f in filters if f.op() in EQUALITY_OPERATORS] + ineq_filters = [f for f in filters if f.op() in INEQUALITY_OPERATORS] + exists_filters = [f for f in filters if f.op() in EXISTS_OPERATORS] + assert (len(eq_filters) + len(ineq_filters) + + len(exists_filters)) == len(filters), 'Not all filters used' + + if (kind and not ineq_filters and not exists_filters and + not orders): + names = set(f.property(0).name() for f in eq_filters) + if not names.intersection(datastore_types._SPECIAL_PROPERTIES): + required = False + + ineq_property = None + if ineq_filters: + for filter in ineq_filters: + if (filter.property(0).name() == + datastore_types._UNAPPLIED_LOG_TIMESTAMP_SPECIAL_PROPERTY): + continue + if not ineq_property: + ineq_property = filter.property(0).name() + else: + assert filter.property(0).name() == ineq_property + + props = [] + + for f in eq_filters: + prop = f.property(0) + props.append((prop.name(), ASCENDING)) + + props.sort() + + if ineq_property: + if orders: + assert ineq_property == orders[0].property() + else: + props.append((ineq_property, ASCENDING)) + + for order in orders: + props.append((order.property(), order.direction())) + + for filter in exists_filters: + prop = filter.property(0) + prop_name = prop.name() + for name, direction in props: + if name == prop_name: + break + else: + props.append((prop_name, ASCENDING)) + + if kind and not ancestor and len(props) <= 1: + required = False + + if props: + prop, dir = props[0] + if prop in datastore_types._SPECIAL_PROPERTIES and dir is DESCENDING: + required = True + + return (required, kind, ancestor, tuple(props), len(eq_filters)) + + +def IndexYamlForQuery(kind, ancestor, props): + """Return the composite index definition YAML needed for a query. + + The arguments are the same as the tuples returned by CompositeIndexForQuery, + without the last neq element. + + Args: + kind: the kind or None + ancestor: True if this is an ancestor query, False otherwise + prop1, prop2, ...: tuples of the form (name, direction) where: + name: a property name; + direction: datastore_pb.Query_Order.ASCENDING or ...DESCENDING; + + Returns: + A string with the YAML for the composite index needed by the query. + """ + yaml = [] + yaml.append('- kind: %s' % kind) + if ancestor: + yaml.append(' ancestor: yes') + if props: + yaml.append(' properties:') + for name, direction in props: + yaml.append(' - name: %s' % name) + if direction == DESCENDING: + yaml.append(' direction: desc') + return '\n'.join(yaml) + + +def IndexDefinitionToProto(app_id, index_definition): + """Transform individual Index definition to protocol buffer. + + Args: + app_id: Application id for new protocol buffer CompositeIndex. + index_definition: datastore_index.Index object to transform. + + Returns: + New entity_pb.CompositeIndex with default values set and index + information filled in. + """ + proto = entity_pb.CompositeIndex() + + proto.set_app_id(app_id) + proto.set_id(0) + proto.set_state(entity_pb.CompositeIndex.WRITE_ONLY) + + definition_proto = proto.mutable_definition() + definition_proto.set_entity_type(index_definition.kind) + definition_proto.set_ancestor(index_definition.ancestor) + + if index_definition.properties is not None: + for prop in index_definition.properties: + prop_proto = definition_proto.add_property() + prop_proto.set_name(prop.name) + prop_proto.set_direction(_DIRECTION_MAP[prop.direction]) + + return proto + + +def IndexDefinitionsToProtos(app_id, index_definitions): + """Transform multiple index definitions to composite index records + + Args: + app_id: Application id for new protocol buffer CompositeIndex. + index_definition: A list of datastore_index.Index objects to transform. + + Returns: + A list of tranformed entity_pb.Compositeindex entities with default values + set and index information filled in. + """ + return [IndexDefinitionToProto(app_id, index) + for index in index_definitions] + + +def ProtoToIndexDefinition(proto): + """Transform individual index protocol buffer to index definition. + + Args: + proto: An instance of entity_pb.CompositeIndex to transform. + + Returns: + A new instance of datastore_index.Index. + """ + properties = [] + proto_index = proto.definition() + for prop_proto in proto_index.property_list(): + prop_definition = Property(name=prop_proto.name()) + if prop_proto.direction() == entity_pb.Index_Property.DESCENDING: + prop_definition.direction = 'descending' + properties.append(prop_definition) + + index = Index(kind=proto_index.entity_type(), properties=properties) + if proto_index.ancestor(): + index.ancestor = True + return index + + +def ProtosToIndexDefinitions(protos): + """Transform multiple index protocol buffers to index definitions. + + Args: + A list of entity_pb.Index records. + """ + return [ProtoToIndexDefinition(definition) for definition in protos] diff --git a/google_appengine/google/appengine/datastore/datastore_index.pyc b/google_appengine/google/appengine/datastore/datastore_index.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee9c9d9f4b18d3aac5fee44c7ffa5a3145a0055a GIT binary patch literal 16942 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/datastore_pb.py b/google_appengine/google/appengine/datastore/datastore_pb.py new file mode 100755 index 0000000..0e19a9c --- /dev/null +++ b/google_appengine/google/appengine/datastore/datastore_pb.py @@ -0,0 +1,5797 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.appengine.api.api_base_pb import Integer64Proto; +from google.appengine.api.api_base_pb import StringProto; +from google.appengine.api.api_base_pb import VoidProto; +from google.appengine.datastore.action_pb import Action +from google.appengine.datastore.entity_pb import CompositeIndex +from google.appengine.datastore.entity_pb import EntityProto +from google.appengine.datastore.entity_pb import Index +from google.appengine.datastore.entity_pb import Property +from google.appengine.datastore.entity_pb import Path +from google.appengine.datastore.entity_pb import Reference +class Transaction(ProtocolBuffer.ProtocolMessage): + has_handle_ = 0 + handle_ = 0 + has_app_ = 0 + app_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def handle(self): return self.handle_ + + def set_handle(self, x): + self.has_handle_ = 1 + self.handle_ = x + + def clear_handle(self): + if self.has_handle_: + self.has_handle_ = 0 + self.handle_ = 0 + + def has_handle(self): return self.has_handle_ + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_handle()): self.set_handle(x.handle()) + if (x.has_app()): self.set_app(x.app()) + + def Equals(self, x): + if x is self: return 1 + if self.has_handle_ != x.has_handle_: return 0 + if self.has_handle_ and self.handle_ != x.handle_: return 0 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_handle_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: handle not set.') + return initialized + + def ByteSize(self): + n = 0 + if (self.has_app_): n += 1 + self.lengthString(len(self.app_)) + return n + 9 + + def Clear(self): + self.clear_handle() + self.clear_app() + + def OutputUnchecked(self, out): + out.putVarInt32(9) + out.put64(self.handle_) + if (self.has_app_): + out.putVarInt32(18) + out.putPrefixedString(self.app_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 9: + self.set_handle(d.get64()) + continue + if tt == 18: + self.set_app(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_handle_: res+=prefix+("handle: %s\n" % self.DebugFormatFixed64(self.handle_)) + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + khandle = 1 + kapp = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "handle", + 2: "app", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.DOUBLE, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Query_Filter(ProtocolBuffer.ProtocolMessage): + + LESS_THAN = 1 + LESS_THAN_OR_EQUAL = 2 + GREATER_THAN = 3 + GREATER_THAN_OR_EQUAL = 4 + EQUAL = 5 + IN = 6 + EXISTS = 7 + + _Operator_NAMES = { + 1: "LESS_THAN", + 2: "LESS_THAN_OR_EQUAL", + 3: "GREATER_THAN", + 4: "GREATER_THAN_OR_EQUAL", + 5: "EQUAL", + 6: "IN", + 7: "EXISTS", + } + + def Operator_Name(cls, x): return cls._Operator_NAMES.get(x, "") + Operator_Name = classmethod(Operator_Name) + + has_op_ = 0 + op_ = 0 + + def __init__(self, contents=None): + self.property_ = [] + if contents is not None: self.MergeFromString(contents) + + def op(self): return self.op_ + + def set_op(self, x): + self.has_op_ = 1 + self.op_ = x + + def clear_op(self): + if self.has_op_: + self.has_op_ = 0 + self.op_ = 0 + + def has_op(self): return self.has_op_ + + def property_size(self): return len(self.property_) + def property_list(self): return self.property_ + + def property(self, i): + return self.property_[i] + + def mutable_property(self, i): + return self.property_[i] + + def add_property(self): + x = Property() + self.property_.append(x) + return x + + def clear_property(self): + self.property_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_op()): self.set_op(x.op()) + for i in xrange(x.property_size()): self.add_property().CopyFrom(x.property(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_op_ != x.has_op_: return 0 + if self.has_op_ and self.op_ != x.op_: return 0 + if len(self.property_) != len(x.property_): return 0 + for e1, e2 in zip(self.property_, x.property_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_op_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: op not set.') + for p in self.property_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.op_) + n += 1 * len(self.property_) + for i in xrange(len(self.property_)): n += self.lengthString(self.property_[i].ByteSize()) + return n + 1 + + def Clear(self): + self.clear_op() + self.clear_property() + + def OutputUnchecked(self, out): + out.putVarInt32(48) + out.putVarInt32(self.op_) + for i in xrange(len(self.property_)): + out.putVarInt32(114) + out.putVarInt32(self.property_[i].ByteSize()) + self.property_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 36: break + if tt == 48: + self.set_op(d.getVarInt32()) + continue + if tt == 114: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_property().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_op_: res+=prefix+("op: %s\n" % self.DebugFormatInt32(self.op_)) + cnt=0 + for e in self.property_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("property%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + +class Query_Order(ProtocolBuffer.ProtocolMessage): + + ASCENDING = 1 + DESCENDING = 2 + + _Direction_NAMES = { + 1: "ASCENDING", + 2: "DESCENDING", + } + + def Direction_Name(cls, x): return cls._Direction_NAMES.get(x, "") + Direction_Name = classmethod(Direction_Name) + + has_property_ = 0 + property_ = "" + has_direction_ = 0 + direction_ = 1 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def property(self): return self.property_ + + def set_property(self, x): + self.has_property_ = 1 + self.property_ = x + + def clear_property(self): + if self.has_property_: + self.has_property_ = 0 + self.property_ = "" + + def has_property(self): return self.has_property_ + + def direction(self): return self.direction_ + + def set_direction(self, x): + self.has_direction_ = 1 + self.direction_ = x + + def clear_direction(self): + if self.has_direction_: + self.has_direction_ = 0 + self.direction_ = 1 + + def has_direction(self): return self.has_direction_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_property()): self.set_property(x.property()) + if (x.has_direction()): self.set_direction(x.direction()) + + def Equals(self, x): + if x is self: return 1 + if self.has_property_ != x.has_property_: return 0 + if self.has_property_ and self.property_ != x.property_: return 0 + if self.has_direction_ != x.has_direction_: return 0 + if self.has_direction_ and self.direction_ != x.direction_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_property_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: property not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.property_)) + if (self.has_direction_): n += 1 + self.lengthVarInt64(self.direction_) + return n + 1 + + def Clear(self): + self.clear_property() + self.clear_direction() + + def OutputUnchecked(self, out): + out.putVarInt32(82) + out.putPrefixedString(self.property_) + if (self.has_direction_): + out.putVarInt32(88) + out.putVarInt32(self.direction_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 76: break + if tt == 82: + self.set_property(d.getPrefixedString()) + continue + if tt == 88: + self.set_direction(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_property_: res+=prefix+("property: %s\n" % self.DebugFormatString(self.property_)) + if self.has_direction_: res+=prefix+("direction: %s\n" % self.DebugFormatInt32(self.direction_)) + return res + +class Query(ProtocolBuffer.ProtocolMessage): + + ORDER_FIRST = 1 + ANCESTOR_FIRST = 2 + FILTER_FIRST = 3 + + _Hint_NAMES = { + 1: "ORDER_FIRST", + 2: "ANCESTOR_FIRST", + 3: "FILTER_FIRST", + } + + def Hint_Name(cls, x): return cls._Hint_NAMES.get(x, "") + Hint_Name = classmethod(Hint_Name) + + has_app_ = 0 + app_ = "" + has_name_space_ = 0 + name_space_ = "" + has_kind_ = 0 + kind_ = "" + has_ancestor_ = 0 + ancestor_ = None + has_search_query_ = 0 + search_query_ = "" + has_hint_ = 0 + hint_ = 0 + has_count_ = 0 + count_ = 0 + has_offset_ = 0 + offset_ = 0 + has_limit_ = 0 + limit_ = 0 + has_compiled_cursor_ = 0 + compiled_cursor_ = None + has_end_compiled_cursor_ = 0 + end_compiled_cursor_ = None + has_require_perfect_plan_ = 0 + require_perfect_plan_ = 0 + has_keys_only_ = 0 + keys_only_ = 0 + has_transaction_ = 0 + transaction_ = None + has_distinct_ = 0 + distinct_ = 0 + has_compile_ = 0 + compile_ = 0 + has_failover_ms_ = 0 + failover_ms_ = 0 + + def __init__(self, contents=None): + self.filter_ = [] + self.order_ = [] + self.composite_index_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def kind(self): return self.kind_ + + def set_kind(self, x): + self.has_kind_ = 1 + self.kind_ = x + + def clear_kind(self): + if self.has_kind_: + self.has_kind_ = 0 + self.kind_ = "" + + def has_kind(self): return self.has_kind_ + + def ancestor(self): + if self.ancestor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.ancestor_ is None: self.ancestor_ = Reference() + finally: + self.lazy_init_lock_.release() + return self.ancestor_ + + def mutable_ancestor(self): self.has_ancestor_ = 1; return self.ancestor() + + def clear_ancestor(self): + if self.has_ancestor_: + self.has_ancestor_ = 0; + if self.ancestor_ is not None: self.ancestor_.Clear() + + def has_ancestor(self): return self.has_ancestor_ + + def filter_size(self): return len(self.filter_) + def filter_list(self): return self.filter_ + + def filter(self, i): + return self.filter_[i] + + def mutable_filter(self, i): + return self.filter_[i] + + def add_filter(self): + x = Query_Filter() + self.filter_.append(x) + return x + + def clear_filter(self): + self.filter_ = [] + def search_query(self): return self.search_query_ + + def set_search_query(self, x): + self.has_search_query_ = 1 + self.search_query_ = x + + def clear_search_query(self): + if self.has_search_query_: + self.has_search_query_ = 0 + self.search_query_ = "" + + def has_search_query(self): return self.has_search_query_ + + def order_size(self): return len(self.order_) + def order_list(self): return self.order_ + + def order(self, i): + return self.order_[i] + + def mutable_order(self, i): + return self.order_[i] + + def add_order(self): + x = Query_Order() + self.order_.append(x) + return x + + def clear_order(self): + self.order_ = [] + def hint(self): return self.hint_ + + def set_hint(self, x): + self.has_hint_ = 1 + self.hint_ = x + + def clear_hint(self): + if self.has_hint_: + self.has_hint_ = 0 + self.hint_ = 0 + + def has_hint(self): return self.has_hint_ + + def count(self): return self.count_ + + def set_count(self, x): + self.has_count_ = 1 + self.count_ = x + + def clear_count(self): + if self.has_count_: + self.has_count_ = 0 + self.count_ = 0 + + def has_count(self): return self.has_count_ + + def offset(self): return self.offset_ + + def set_offset(self, x): + self.has_offset_ = 1 + self.offset_ = x + + def clear_offset(self): + if self.has_offset_: + self.has_offset_ = 0 + self.offset_ = 0 + + def has_offset(self): return self.has_offset_ + + def limit(self): return self.limit_ + + def set_limit(self, x): + self.has_limit_ = 1 + self.limit_ = x + + def clear_limit(self): + if self.has_limit_: + self.has_limit_ = 0 + self.limit_ = 0 + + def has_limit(self): return self.has_limit_ + + def compiled_cursor(self): + if self.compiled_cursor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.compiled_cursor_ is None: self.compiled_cursor_ = CompiledCursor() + finally: + self.lazy_init_lock_.release() + return self.compiled_cursor_ + + def mutable_compiled_cursor(self): self.has_compiled_cursor_ = 1; return self.compiled_cursor() + + def clear_compiled_cursor(self): + if self.has_compiled_cursor_: + self.has_compiled_cursor_ = 0; + if self.compiled_cursor_ is not None: self.compiled_cursor_.Clear() + + def has_compiled_cursor(self): return self.has_compiled_cursor_ + + def end_compiled_cursor(self): + if self.end_compiled_cursor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.end_compiled_cursor_ is None: self.end_compiled_cursor_ = CompiledCursor() + finally: + self.lazy_init_lock_.release() + return self.end_compiled_cursor_ + + def mutable_end_compiled_cursor(self): self.has_end_compiled_cursor_ = 1; return self.end_compiled_cursor() + + def clear_end_compiled_cursor(self): + if self.has_end_compiled_cursor_: + self.has_end_compiled_cursor_ = 0; + if self.end_compiled_cursor_ is not None: self.end_compiled_cursor_.Clear() + + def has_end_compiled_cursor(self): return self.has_end_compiled_cursor_ + + def composite_index_size(self): return len(self.composite_index_) + def composite_index_list(self): return self.composite_index_ + + def composite_index(self, i): + return self.composite_index_[i] + + def mutable_composite_index(self, i): + return self.composite_index_[i] + + def add_composite_index(self): + x = CompositeIndex() + self.composite_index_.append(x) + return x + + def clear_composite_index(self): + self.composite_index_ = [] + def require_perfect_plan(self): return self.require_perfect_plan_ + + def set_require_perfect_plan(self, x): + self.has_require_perfect_plan_ = 1 + self.require_perfect_plan_ = x + + def clear_require_perfect_plan(self): + if self.has_require_perfect_plan_: + self.has_require_perfect_plan_ = 0 + self.require_perfect_plan_ = 0 + + def has_require_perfect_plan(self): return self.has_require_perfect_plan_ + + def keys_only(self): return self.keys_only_ + + def set_keys_only(self, x): + self.has_keys_only_ = 1 + self.keys_only_ = x + + def clear_keys_only(self): + if self.has_keys_only_: + self.has_keys_only_ = 0 + self.keys_only_ = 0 + + def has_keys_only(self): return self.has_keys_only_ + + def transaction(self): + if self.transaction_ is None: + self.lazy_init_lock_.acquire() + try: + if self.transaction_ is None: self.transaction_ = Transaction() + finally: + self.lazy_init_lock_.release() + return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction() + + def clear_transaction(self): + if self.has_transaction_: + self.has_transaction_ = 0; + if self.transaction_ is not None: self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def distinct(self): return self.distinct_ + + def set_distinct(self, x): + self.has_distinct_ = 1 + self.distinct_ = x + + def clear_distinct(self): + if self.has_distinct_: + self.has_distinct_ = 0 + self.distinct_ = 0 + + def has_distinct(self): return self.has_distinct_ + + def compile(self): return self.compile_ + + def set_compile(self, x): + self.has_compile_ = 1 + self.compile_ = x + + def clear_compile(self): + if self.has_compile_: + self.has_compile_ = 0 + self.compile_ = 0 + + def has_compile(self): return self.has_compile_ + + def failover_ms(self): return self.failover_ms_ + + def set_failover_ms(self, x): + self.has_failover_ms_ = 1 + self.failover_ms_ = x + + def clear_failover_ms(self): + if self.has_failover_ms_: + self.has_failover_ms_ = 0 + self.failover_ms_ = 0 + + def has_failover_ms(self): return self.has_failover_ms_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_kind()): self.set_kind(x.kind()) + if (x.has_ancestor()): self.mutable_ancestor().MergeFrom(x.ancestor()) + for i in xrange(x.filter_size()): self.add_filter().CopyFrom(x.filter(i)) + if (x.has_search_query()): self.set_search_query(x.search_query()) + for i in xrange(x.order_size()): self.add_order().CopyFrom(x.order(i)) + if (x.has_hint()): self.set_hint(x.hint()) + if (x.has_count()): self.set_count(x.count()) + if (x.has_offset()): self.set_offset(x.offset()) + if (x.has_limit()): self.set_limit(x.limit()) + if (x.has_compiled_cursor()): self.mutable_compiled_cursor().MergeFrom(x.compiled_cursor()) + if (x.has_end_compiled_cursor()): self.mutable_end_compiled_cursor().MergeFrom(x.end_compiled_cursor()) + for i in xrange(x.composite_index_size()): self.add_composite_index().CopyFrom(x.composite_index(i)) + if (x.has_require_perfect_plan()): self.set_require_perfect_plan(x.require_perfect_plan()) + if (x.has_keys_only()): self.set_keys_only(x.keys_only()) + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + if (x.has_distinct()): self.set_distinct(x.distinct()) + if (x.has_compile()): self.set_compile(x.compile()) + if (x.has_failover_ms()): self.set_failover_ms(x.failover_ms()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_kind_ != x.has_kind_: return 0 + if self.has_kind_ and self.kind_ != x.kind_: return 0 + if self.has_ancestor_ != x.has_ancestor_: return 0 + if self.has_ancestor_ and self.ancestor_ != x.ancestor_: return 0 + if len(self.filter_) != len(x.filter_): return 0 + for e1, e2 in zip(self.filter_, x.filter_): + if e1 != e2: return 0 + if self.has_search_query_ != x.has_search_query_: return 0 + if self.has_search_query_ and self.search_query_ != x.search_query_: return 0 + if len(self.order_) != len(x.order_): return 0 + for e1, e2 in zip(self.order_, x.order_): + if e1 != e2: return 0 + if self.has_hint_ != x.has_hint_: return 0 + if self.has_hint_ and self.hint_ != x.hint_: return 0 + if self.has_count_ != x.has_count_: return 0 + if self.has_count_ and self.count_ != x.count_: return 0 + if self.has_offset_ != x.has_offset_: return 0 + if self.has_offset_ and self.offset_ != x.offset_: return 0 + if self.has_limit_ != x.has_limit_: return 0 + if self.has_limit_ and self.limit_ != x.limit_: return 0 + if self.has_compiled_cursor_ != x.has_compiled_cursor_: return 0 + if self.has_compiled_cursor_ and self.compiled_cursor_ != x.compiled_cursor_: return 0 + if self.has_end_compiled_cursor_ != x.has_end_compiled_cursor_: return 0 + if self.has_end_compiled_cursor_ and self.end_compiled_cursor_ != x.end_compiled_cursor_: return 0 + if len(self.composite_index_) != len(x.composite_index_): return 0 + for e1, e2 in zip(self.composite_index_, x.composite_index_): + if e1 != e2: return 0 + if self.has_require_perfect_plan_ != x.has_require_perfect_plan_: return 0 + if self.has_require_perfect_plan_ and self.require_perfect_plan_ != x.require_perfect_plan_: return 0 + if self.has_keys_only_ != x.has_keys_only_: return 0 + if self.has_keys_only_ and self.keys_only_ != x.keys_only_: return 0 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if self.has_distinct_ != x.has_distinct_: return 0 + if self.has_distinct_ and self.distinct_ != x.distinct_: return 0 + if self.has_compile_ != x.has_compile_: return 0 + if self.has_compile_ and self.compile_ != x.compile_: return 0 + if self.has_failover_ms_ != x.has_failover_ms_: return 0 + if self.has_failover_ms_ and self.failover_ms_ != x.failover_ms_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app not set.') + if (self.has_ancestor_ and not self.ancestor_.IsInitialized(debug_strs)): initialized = 0 + for p in self.filter_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.order_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_compiled_cursor_ and not self.compiled_cursor_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_end_compiled_cursor_ and not self.end_compiled_cursor_.IsInitialized(debug_strs)): initialized = 0 + for p in self.composite_index_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_transaction_ and not self.transaction_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_)) + if (self.has_name_space_): n += 2 + self.lengthString(len(self.name_space_)) + if (self.has_kind_): n += 1 + self.lengthString(len(self.kind_)) + if (self.has_ancestor_): n += 2 + self.lengthString(self.ancestor_.ByteSize()) + n += 2 * len(self.filter_) + for i in xrange(len(self.filter_)): n += self.filter_[i].ByteSize() + if (self.has_search_query_): n += 1 + self.lengthString(len(self.search_query_)) + n += 2 * len(self.order_) + for i in xrange(len(self.order_)): n += self.order_[i].ByteSize() + if (self.has_hint_): n += 2 + self.lengthVarInt64(self.hint_) + if (self.has_count_): n += 2 + self.lengthVarInt64(self.count_) + if (self.has_offset_): n += 1 + self.lengthVarInt64(self.offset_) + if (self.has_limit_): n += 2 + self.lengthVarInt64(self.limit_) + if (self.has_compiled_cursor_): n += 2 + self.lengthString(self.compiled_cursor_.ByteSize()) + if (self.has_end_compiled_cursor_): n += 2 + self.lengthString(self.end_compiled_cursor_.ByteSize()) + n += 2 * len(self.composite_index_) + for i in xrange(len(self.composite_index_)): n += self.lengthString(self.composite_index_[i].ByteSize()) + if (self.has_require_perfect_plan_): n += 3 + if (self.has_keys_only_): n += 3 + if (self.has_transaction_): n += 2 + self.lengthString(self.transaction_.ByteSize()) + if (self.has_distinct_): n += 3 + if (self.has_compile_): n += 3 + if (self.has_failover_ms_): n += 2 + self.lengthVarInt64(self.failover_ms_) + return n + 1 + + def Clear(self): + self.clear_app() + self.clear_name_space() + self.clear_kind() + self.clear_ancestor() + self.clear_filter() + self.clear_search_query() + self.clear_order() + self.clear_hint() + self.clear_count() + self.clear_offset() + self.clear_limit() + self.clear_compiled_cursor() + self.clear_end_compiled_cursor() + self.clear_composite_index() + self.clear_require_perfect_plan() + self.clear_keys_only() + self.clear_transaction() + self.clear_distinct() + self.clear_compile() + self.clear_failover_ms() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_) + if (self.has_kind_): + out.putVarInt32(26) + out.putPrefixedString(self.kind_) + for i in xrange(len(self.filter_)): + out.putVarInt32(35) + self.filter_[i].OutputUnchecked(out) + out.putVarInt32(36) + if (self.has_search_query_): + out.putVarInt32(66) + out.putPrefixedString(self.search_query_) + for i in xrange(len(self.order_)): + out.putVarInt32(75) + self.order_[i].OutputUnchecked(out) + out.putVarInt32(76) + if (self.has_offset_): + out.putVarInt32(96) + out.putVarInt32(self.offset_) + if (self.has_limit_): + out.putVarInt32(128) + out.putVarInt32(self.limit_) + if (self.has_ancestor_): + out.putVarInt32(138) + out.putVarInt32(self.ancestor_.ByteSize()) + self.ancestor_.OutputUnchecked(out) + if (self.has_hint_): + out.putVarInt32(144) + out.putVarInt32(self.hint_) + for i in xrange(len(self.composite_index_)): + out.putVarInt32(154) + out.putVarInt32(self.composite_index_[i].ByteSize()) + self.composite_index_[i].OutputUnchecked(out) + if (self.has_require_perfect_plan_): + out.putVarInt32(160) + out.putBoolean(self.require_perfect_plan_) + if (self.has_keys_only_): + out.putVarInt32(168) + out.putBoolean(self.keys_only_) + if (self.has_transaction_): + out.putVarInt32(178) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + if (self.has_count_): + out.putVarInt32(184) + out.putVarInt32(self.count_) + if (self.has_distinct_): + out.putVarInt32(192) + out.putBoolean(self.distinct_) + if (self.has_compile_): + out.putVarInt32(200) + out.putBoolean(self.compile_) + if (self.has_failover_ms_): + out.putVarInt32(208) + out.putVarInt64(self.failover_ms_) + if (self.has_name_space_): + out.putVarInt32(234) + out.putPrefixedString(self.name_space_) + if (self.has_compiled_cursor_): + out.putVarInt32(242) + out.putVarInt32(self.compiled_cursor_.ByteSize()) + self.compiled_cursor_.OutputUnchecked(out) + if (self.has_end_compiled_cursor_): + out.putVarInt32(250) + out.putVarInt32(self.end_compiled_cursor_.ByteSize()) + self.end_compiled_cursor_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app(d.getPrefixedString()) + continue + if tt == 26: + self.set_kind(d.getPrefixedString()) + continue + if tt == 35: + self.add_filter().TryMerge(d) + continue + if tt == 66: + self.set_search_query(d.getPrefixedString()) + continue + if tt == 75: + self.add_order().TryMerge(d) + continue + if tt == 96: + self.set_offset(d.getVarInt32()) + continue + if tt == 128: + self.set_limit(d.getVarInt32()) + continue + if tt == 138: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_ancestor().TryMerge(tmp) + continue + if tt == 144: + self.set_hint(d.getVarInt32()) + continue + if tt == 154: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_composite_index().TryMerge(tmp) + continue + if tt == 160: + self.set_require_perfect_plan(d.getBoolean()) + continue + if tt == 168: + self.set_keys_only(d.getBoolean()) + continue + if tt == 178: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 184: + self.set_count(d.getVarInt32()) + continue + if tt == 192: + self.set_distinct(d.getBoolean()) + continue + if tt == 200: + self.set_compile(d.getBoolean()) + continue + if tt == 208: + self.set_failover_ms(d.getVarInt64()) + continue + if tt == 234: + self.set_name_space(d.getPrefixedString()) + continue + if tt == 242: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_compiled_cursor().TryMerge(tmp) + continue + if tt == 250: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_end_compiled_cursor().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_kind_: res+=prefix+("kind: %s\n" % self.DebugFormatString(self.kind_)) + if self.has_ancestor_: + res+=prefix+"ancestor <\n" + res+=self.ancestor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.filter_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Filter%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_search_query_: res+=prefix+("search_query: %s\n" % self.DebugFormatString(self.search_query_)) + cnt=0 + for e in self.order_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Order%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_hint_: res+=prefix+("hint: %s\n" % self.DebugFormatInt32(self.hint_)) + if self.has_count_: res+=prefix+("count: %s\n" % self.DebugFormatInt32(self.count_)) + if self.has_offset_: res+=prefix+("offset: %s\n" % self.DebugFormatInt32(self.offset_)) + if self.has_limit_: res+=prefix+("limit: %s\n" % self.DebugFormatInt32(self.limit_)) + if self.has_compiled_cursor_: + res+=prefix+"compiled_cursor <\n" + res+=self.compiled_cursor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_end_compiled_cursor_: + res+=prefix+"end_compiled_cursor <\n" + res+=self.end_compiled_cursor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.composite_index_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("composite_index%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_require_perfect_plan_: res+=prefix+("require_perfect_plan: %s\n" % self.DebugFormatBool(self.require_perfect_plan_)) + if self.has_keys_only_: res+=prefix+("keys_only: %s\n" % self.DebugFormatBool(self.keys_only_)) + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_distinct_: res+=prefix+("distinct: %s\n" % self.DebugFormatBool(self.distinct_)) + if self.has_compile_: res+=prefix+("compile: %s\n" % self.DebugFormatBool(self.compile_)) + if self.has_failover_ms_: res+=prefix+("failover_ms: %s\n" % self.DebugFormatInt64(self.failover_ms_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 1 + kname_space = 29 + kkind = 3 + kancestor = 17 + kFilterGroup = 4 + kFilterop = 6 + kFilterproperty = 14 + ksearch_query = 8 + kOrderGroup = 9 + kOrderproperty = 10 + kOrderdirection = 11 + khint = 18 + kcount = 23 + koffset = 12 + klimit = 16 + kcompiled_cursor = 30 + kend_compiled_cursor = 31 + kcomposite_index = 19 + krequire_perfect_plan = 20 + kkeys_only = 21 + ktransaction = 22 + kdistinct = 24 + kcompile = 25 + kfailover_ms = 26 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app", + 3: "kind", + 4: "Filter", + 6: "op", + 8: "search_query", + 9: "Order", + 10: "property", + 11: "direction", + 12: "offset", + 14: "property", + 16: "limit", + 17: "ancestor", + 18: "hint", + 19: "composite_index", + 20: "require_perfect_plan", + 21: "keys_only", + 22: "transaction", + 23: "count", + 24: "distinct", + 25: "compile", + 26: "failover_ms", + 29: "name_space", + 30: "compiled_cursor", + 31: "end_compiled_cursor", + }, 31) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STARTGROUP, + 6: ProtocolBuffer.Encoder.NUMERIC, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.STARTGROUP, + 10: ProtocolBuffer.Encoder.STRING, + 11: ProtocolBuffer.Encoder.NUMERIC, + 12: ProtocolBuffer.Encoder.NUMERIC, + 14: ProtocolBuffer.Encoder.STRING, + 16: ProtocolBuffer.Encoder.NUMERIC, + 17: ProtocolBuffer.Encoder.STRING, + 18: ProtocolBuffer.Encoder.NUMERIC, + 19: ProtocolBuffer.Encoder.STRING, + 20: ProtocolBuffer.Encoder.NUMERIC, + 21: ProtocolBuffer.Encoder.NUMERIC, + 22: ProtocolBuffer.Encoder.STRING, + 23: ProtocolBuffer.Encoder.NUMERIC, + 24: ProtocolBuffer.Encoder.NUMERIC, + 25: ProtocolBuffer.Encoder.NUMERIC, + 26: ProtocolBuffer.Encoder.NUMERIC, + 29: ProtocolBuffer.Encoder.STRING, + 30: ProtocolBuffer.Encoder.STRING, + 31: ProtocolBuffer.Encoder.STRING, + }, 31, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompiledQuery_PrimaryScan(ProtocolBuffer.ProtocolMessage): + has_index_name_ = 0 + index_name_ = "" + has_start_key_ = 0 + start_key_ = "" + has_start_inclusive_ = 0 + start_inclusive_ = 0 + has_end_key_ = 0 + end_key_ = "" + has_end_inclusive_ = 0 + end_inclusive_ = 0 + has_end_unapplied_log_timestamp_us_ = 0 + end_unapplied_log_timestamp_us_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def index_name(self): return self.index_name_ + + def set_index_name(self, x): + self.has_index_name_ = 1 + self.index_name_ = x + + def clear_index_name(self): + if self.has_index_name_: + self.has_index_name_ = 0 + self.index_name_ = "" + + def has_index_name(self): return self.has_index_name_ + + def start_key(self): return self.start_key_ + + def set_start_key(self, x): + self.has_start_key_ = 1 + self.start_key_ = x + + def clear_start_key(self): + if self.has_start_key_: + self.has_start_key_ = 0 + self.start_key_ = "" + + def has_start_key(self): return self.has_start_key_ + + def start_inclusive(self): return self.start_inclusive_ + + def set_start_inclusive(self, x): + self.has_start_inclusive_ = 1 + self.start_inclusive_ = x + + def clear_start_inclusive(self): + if self.has_start_inclusive_: + self.has_start_inclusive_ = 0 + self.start_inclusive_ = 0 + + def has_start_inclusive(self): return self.has_start_inclusive_ + + def end_key(self): return self.end_key_ + + def set_end_key(self, x): + self.has_end_key_ = 1 + self.end_key_ = x + + def clear_end_key(self): + if self.has_end_key_: + self.has_end_key_ = 0 + self.end_key_ = "" + + def has_end_key(self): return self.has_end_key_ + + def end_inclusive(self): return self.end_inclusive_ + + def set_end_inclusive(self, x): + self.has_end_inclusive_ = 1 + self.end_inclusive_ = x + + def clear_end_inclusive(self): + if self.has_end_inclusive_: + self.has_end_inclusive_ = 0 + self.end_inclusive_ = 0 + + def has_end_inclusive(self): return self.has_end_inclusive_ + + def end_unapplied_log_timestamp_us(self): return self.end_unapplied_log_timestamp_us_ + + def set_end_unapplied_log_timestamp_us(self, x): + self.has_end_unapplied_log_timestamp_us_ = 1 + self.end_unapplied_log_timestamp_us_ = x + + def clear_end_unapplied_log_timestamp_us(self): + if self.has_end_unapplied_log_timestamp_us_: + self.has_end_unapplied_log_timestamp_us_ = 0 + self.end_unapplied_log_timestamp_us_ = 0 + + def has_end_unapplied_log_timestamp_us(self): return self.has_end_unapplied_log_timestamp_us_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_index_name()): self.set_index_name(x.index_name()) + if (x.has_start_key()): self.set_start_key(x.start_key()) + if (x.has_start_inclusive()): self.set_start_inclusive(x.start_inclusive()) + if (x.has_end_key()): self.set_end_key(x.end_key()) + if (x.has_end_inclusive()): self.set_end_inclusive(x.end_inclusive()) + if (x.has_end_unapplied_log_timestamp_us()): self.set_end_unapplied_log_timestamp_us(x.end_unapplied_log_timestamp_us()) + + def Equals(self, x): + if x is self: return 1 + if self.has_index_name_ != x.has_index_name_: return 0 + if self.has_index_name_ and self.index_name_ != x.index_name_: return 0 + if self.has_start_key_ != x.has_start_key_: return 0 + if self.has_start_key_ and self.start_key_ != x.start_key_: return 0 + if self.has_start_inclusive_ != x.has_start_inclusive_: return 0 + if self.has_start_inclusive_ and self.start_inclusive_ != x.start_inclusive_: return 0 + if self.has_end_key_ != x.has_end_key_: return 0 + if self.has_end_key_ and self.end_key_ != x.end_key_: return 0 + if self.has_end_inclusive_ != x.has_end_inclusive_: return 0 + if self.has_end_inclusive_ and self.end_inclusive_ != x.end_inclusive_: return 0 + if self.has_end_unapplied_log_timestamp_us_ != x.has_end_unapplied_log_timestamp_us_: return 0 + if self.has_end_unapplied_log_timestamp_us_ and self.end_unapplied_log_timestamp_us_ != x.end_unapplied_log_timestamp_us_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_index_name_): n += 1 + self.lengthString(len(self.index_name_)) + if (self.has_start_key_): n += 1 + self.lengthString(len(self.start_key_)) + if (self.has_start_inclusive_): n += 2 + if (self.has_end_key_): n += 1 + self.lengthString(len(self.end_key_)) + if (self.has_end_inclusive_): n += 2 + if (self.has_end_unapplied_log_timestamp_us_): n += 2 + self.lengthVarInt64(self.end_unapplied_log_timestamp_us_) + return n + 0 + + def Clear(self): + self.clear_index_name() + self.clear_start_key() + self.clear_start_inclusive() + self.clear_end_key() + self.clear_end_inclusive() + self.clear_end_unapplied_log_timestamp_us() + + def OutputUnchecked(self, out): + if (self.has_index_name_): + out.putVarInt32(18) + out.putPrefixedString(self.index_name_) + if (self.has_start_key_): + out.putVarInt32(26) + out.putPrefixedString(self.start_key_) + if (self.has_start_inclusive_): + out.putVarInt32(32) + out.putBoolean(self.start_inclusive_) + if (self.has_end_key_): + out.putVarInt32(42) + out.putPrefixedString(self.end_key_) + if (self.has_end_inclusive_): + out.putVarInt32(48) + out.putBoolean(self.end_inclusive_) + if (self.has_end_unapplied_log_timestamp_us_): + out.putVarInt32(152) + out.putVarInt64(self.end_unapplied_log_timestamp_us_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_index_name(d.getPrefixedString()) + continue + if tt == 26: + self.set_start_key(d.getPrefixedString()) + continue + if tt == 32: + self.set_start_inclusive(d.getBoolean()) + continue + if tt == 42: + self.set_end_key(d.getPrefixedString()) + continue + if tt == 48: + self.set_end_inclusive(d.getBoolean()) + continue + if tt == 152: + self.set_end_unapplied_log_timestamp_us(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_index_name_: res+=prefix+("index_name: %s\n" % self.DebugFormatString(self.index_name_)) + if self.has_start_key_: res+=prefix+("start_key: %s\n" % self.DebugFormatString(self.start_key_)) + if self.has_start_inclusive_: res+=prefix+("start_inclusive: %s\n" % self.DebugFormatBool(self.start_inclusive_)) + if self.has_end_key_: res+=prefix+("end_key: %s\n" % self.DebugFormatString(self.end_key_)) + if self.has_end_inclusive_: res+=prefix+("end_inclusive: %s\n" % self.DebugFormatBool(self.end_inclusive_)) + if self.has_end_unapplied_log_timestamp_us_: res+=prefix+("end_unapplied_log_timestamp_us: %s\n" % self.DebugFormatInt64(self.end_unapplied_log_timestamp_us_)) + return res + +class CompiledQuery_MergeJoinScan(ProtocolBuffer.ProtocolMessage): + has_index_name_ = 0 + index_name_ = "" + + def __init__(self, contents=None): + self.prefix_value_ = [] + if contents is not None: self.MergeFromString(contents) + + def index_name(self): return self.index_name_ + + def set_index_name(self, x): + self.has_index_name_ = 1 + self.index_name_ = x + + def clear_index_name(self): + if self.has_index_name_: + self.has_index_name_ = 0 + self.index_name_ = "" + + def has_index_name(self): return self.has_index_name_ + + def prefix_value_size(self): return len(self.prefix_value_) + def prefix_value_list(self): return self.prefix_value_ + + def prefix_value(self, i): + return self.prefix_value_[i] + + def set_prefix_value(self, i, x): + self.prefix_value_[i] = x + + def add_prefix_value(self, x): + self.prefix_value_.append(x) + + def clear_prefix_value(self): + self.prefix_value_ = [] + + + def MergeFrom(self, x): + assert x is not self + if (x.has_index_name()): self.set_index_name(x.index_name()) + for i in xrange(x.prefix_value_size()): self.add_prefix_value(x.prefix_value(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_index_name_ != x.has_index_name_: return 0 + if self.has_index_name_ and self.index_name_ != x.index_name_: return 0 + if len(self.prefix_value_) != len(x.prefix_value_): return 0 + for e1, e2 in zip(self.prefix_value_, x.prefix_value_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_index_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: index_name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.index_name_)) + n += 1 * len(self.prefix_value_) + for i in xrange(len(self.prefix_value_)): n += self.lengthString(len(self.prefix_value_[i])) + return n + 1 + + def Clear(self): + self.clear_index_name() + self.clear_prefix_value() + + def OutputUnchecked(self, out): + out.putVarInt32(66) + out.putPrefixedString(self.index_name_) + for i in xrange(len(self.prefix_value_)): + out.putVarInt32(74) + out.putPrefixedString(self.prefix_value_[i]) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 60: break + if tt == 66: + self.set_index_name(d.getPrefixedString()) + continue + if tt == 74: + self.add_prefix_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_index_name_: res+=prefix+("index_name: %s\n" % self.DebugFormatString(self.index_name_)) + cnt=0 + for e in self.prefix_value_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("prefix_value%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + +class CompiledQuery_EntityFilter(ProtocolBuffer.ProtocolMessage): + has_distinct_ = 0 + distinct_ = 0 + has_kind_ = 0 + kind_ = "" + has_ancestor_ = 0 + ancestor_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def distinct(self): return self.distinct_ + + def set_distinct(self, x): + self.has_distinct_ = 1 + self.distinct_ = x + + def clear_distinct(self): + if self.has_distinct_: + self.has_distinct_ = 0 + self.distinct_ = 0 + + def has_distinct(self): return self.has_distinct_ + + def kind(self): return self.kind_ + + def set_kind(self, x): + self.has_kind_ = 1 + self.kind_ = x + + def clear_kind(self): + if self.has_kind_: + self.has_kind_ = 0 + self.kind_ = "" + + def has_kind(self): return self.has_kind_ + + def ancestor(self): + if self.ancestor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.ancestor_ is None: self.ancestor_ = Reference() + finally: + self.lazy_init_lock_.release() + return self.ancestor_ + + def mutable_ancestor(self): self.has_ancestor_ = 1; return self.ancestor() + + def clear_ancestor(self): + if self.has_ancestor_: + self.has_ancestor_ = 0; + if self.ancestor_ is not None: self.ancestor_.Clear() + + def has_ancestor(self): return self.has_ancestor_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_distinct()): self.set_distinct(x.distinct()) + if (x.has_kind()): self.set_kind(x.kind()) + if (x.has_ancestor()): self.mutable_ancestor().MergeFrom(x.ancestor()) + + def Equals(self, x): + if x is self: return 1 + if self.has_distinct_ != x.has_distinct_: return 0 + if self.has_distinct_ and self.distinct_ != x.distinct_: return 0 + if self.has_kind_ != x.has_kind_: return 0 + if self.has_kind_ and self.kind_ != x.kind_: return 0 + if self.has_ancestor_ != x.has_ancestor_: return 0 + if self.has_ancestor_ and self.ancestor_ != x.ancestor_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_ancestor_ and not self.ancestor_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_distinct_): n += 2 + if (self.has_kind_): n += 2 + self.lengthString(len(self.kind_)) + if (self.has_ancestor_): n += 2 + self.lengthString(self.ancestor_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_distinct() + self.clear_kind() + self.clear_ancestor() + + def OutputUnchecked(self, out): + if (self.has_distinct_): + out.putVarInt32(112) + out.putBoolean(self.distinct_) + if (self.has_kind_): + out.putVarInt32(138) + out.putPrefixedString(self.kind_) + if (self.has_ancestor_): + out.putVarInt32(146) + out.putVarInt32(self.ancestor_.ByteSize()) + self.ancestor_.OutputUnchecked(out) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 108: break + if tt == 112: + self.set_distinct(d.getBoolean()) + continue + if tt == 138: + self.set_kind(d.getPrefixedString()) + continue + if tt == 146: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_ancestor().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_distinct_: res+=prefix+("distinct: %s\n" % self.DebugFormatBool(self.distinct_)) + if self.has_kind_: res+=prefix+("kind: %s\n" % self.DebugFormatString(self.kind_)) + if self.has_ancestor_: + res+=prefix+"ancestor <\n" + res+=self.ancestor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + +class CompiledQuery(ProtocolBuffer.ProtocolMessage): + has_primaryscan_ = 0 + has_offset_ = 0 + offset_ = 0 + has_limit_ = 0 + limit_ = 0 + has_keys_only_ = 0 + keys_only_ = 0 + has_entityfilter_ = 0 + entityfilter_ = None + + def __init__(self, contents=None): + self.primaryscan_ = CompiledQuery_PrimaryScan() + self.mergejoinscan_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def primaryscan(self): return self.primaryscan_ + + def mutable_primaryscan(self): self.has_primaryscan_ = 1; return self.primaryscan_ + + def clear_primaryscan(self):self.has_primaryscan_ = 0; self.primaryscan_.Clear() + + def has_primaryscan(self): return self.has_primaryscan_ + + def mergejoinscan_size(self): return len(self.mergejoinscan_) + def mergejoinscan_list(self): return self.mergejoinscan_ + + def mergejoinscan(self, i): + return self.mergejoinscan_[i] + + def mutable_mergejoinscan(self, i): + return self.mergejoinscan_[i] + + def add_mergejoinscan(self): + x = CompiledQuery_MergeJoinScan() + self.mergejoinscan_.append(x) + return x + + def clear_mergejoinscan(self): + self.mergejoinscan_ = [] + def offset(self): return self.offset_ + + def set_offset(self, x): + self.has_offset_ = 1 + self.offset_ = x + + def clear_offset(self): + if self.has_offset_: + self.has_offset_ = 0 + self.offset_ = 0 + + def has_offset(self): return self.has_offset_ + + def limit(self): return self.limit_ + + def set_limit(self, x): + self.has_limit_ = 1 + self.limit_ = x + + def clear_limit(self): + if self.has_limit_: + self.has_limit_ = 0 + self.limit_ = 0 + + def has_limit(self): return self.has_limit_ + + def keys_only(self): return self.keys_only_ + + def set_keys_only(self, x): + self.has_keys_only_ = 1 + self.keys_only_ = x + + def clear_keys_only(self): + if self.has_keys_only_: + self.has_keys_only_ = 0 + self.keys_only_ = 0 + + def has_keys_only(self): return self.has_keys_only_ + + def entityfilter(self): + if self.entityfilter_ is None: + self.lazy_init_lock_.acquire() + try: + if self.entityfilter_ is None: self.entityfilter_ = CompiledQuery_EntityFilter() + finally: + self.lazy_init_lock_.release() + return self.entityfilter_ + + def mutable_entityfilter(self): self.has_entityfilter_ = 1; return self.entityfilter() + + def clear_entityfilter(self): + if self.has_entityfilter_: + self.has_entityfilter_ = 0; + if self.entityfilter_ is not None: self.entityfilter_.Clear() + + def has_entityfilter(self): return self.has_entityfilter_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_primaryscan()): self.mutable_primaryscan().MergeFrom(x.primaryscan()) + for i in xrange(x.mergejoinscan_size()): self.add_mergejoinscan().CopyFrom(x.mergejoinscan(i)) + if (x.has_offset()): self.set_offset(x.offset()) + if (x.has_limit()): self.set_limit(x.limit()) + if (x.has_keys_only()): self.set_keys_only(x.keys_only()) + if (x.has_entityfilter()): self.mutable_entityfilter().MergeFrom(x.entityfilter()) + + def Equals(self, x): + if x is self: return 1 + if self.has_primaryscan_ != x.has_primaryscan_: return 0 + if self.has_primaryscan_ and self.primaryscan_ != x.primaryscan_: return 0 + if len(self.mergejoinscan_) != len(x.mergejoinscan_): return 0 + for e1, e2 in zip(self.mergejoinscan_, x.mergejoinscan_): + if e1 != e2: return 0 + if self.has_offset_ != x.has_offset_: return 0 + if self.has_offset_ and self.offset_ != x.offset_: return 0 + if self.has_limit_ != x.has_limit_: return 0 + if self.has_limit_ and self.limit_ != x.limit_: return 0 + if self.has_keys_only_ != x.has_keys_only_: return 0 + if self.has_keys_only_ and self.keys_only_ != x.keys_only_: return 0 + if self.has_entityfilter_ != x.has_entityfilter_: return 0 + if self.has_entityfilter_ and self.entityfilter_ != x.entityfilter_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_primaryscan_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: primaryscan not set.') + elif not self.primaryscan_.IsInitialized(debug_strs): initialized = 0 + for p in self.mergejoinscan_: + if not p.IsInitialized(debug_strs): initialized=0 + if (not self.has_keys_only_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: keys_only not set.') + if (self.has_entityfilter_ and not self.entityfilter_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.primaryscan_.ByteSize() + n += 2 * len(self.mergejoinscan_) + for i in xrange(len(self.mergejoinscan_)): n += self.mergejoinscan_[i].ByteSize() + if (self.has_offset_): n += 1 + self.lengthVarInt64(self.offset_) + if (self.has_limit_): n += 1 + self.lengthVarInt64(self.limit_) + if (self.has_entityfilter_): n += 2 + self.entityfilter_.ByteSize() + return n + 4 + + def Clear(self): + self.clear_primaryscan() + self.clear_mergejoinscan() + self.clear_offset() + self.clear_limit() + self.clear_keys_only() + self.clear_entityfilter() + + def OutputUnchecked(self, out): + out.putVarInt32(11) + self.primaryscan_.OutputUnchecked(out) + out.putVarInt32(12) + for i in xrange(len(self.mergejoinscan_)): + out.putVarInt32(59) + self.mergejoinscan_[i].OutputUnchecked(out) + out.putVarInt32(60) + if (self.has_offset_): + out.putVarInt32(80) + out.putVarInt32(self.offset_) + if (self.has_limit_): + out.putVarInt32(88) + out.putVarInt32(self.limit_) + out.putVarInt32(96) + out.putBoolean(self.keys_only_) + if (self.has_entityfilter_): + out.putVarInt32(107) + self.entityfilter_.OutputUnchecked(out) + out.putVarInt32(108) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.mutable_primaryscan().TryMerge(d) + continue + if tt == 59: + self.add_mergejoinscan().TryMerge(d) + continue + if tt == 80: + self.set_offset(d.getVarInt32()) + continue + if tt == 88: + self.set_limit(d.getVarInt32()) + continue + if tt == 96: + self.set_keys_only(d.getBoolean()) + continue + if tt == 107: + self.mutable_entityfilter().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_primaryscan_: + res+=prefix+"PrimaryScan {\n" + res+=self.primaryscan_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt=0 + for e in self.mergejoinscan_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("MergeJoinScan%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_offset_: res+=prefix+("offset: %s\n" % self.DebugFormatInt32(self.offset_)) + if self.has_limit_: res+=prefix+("limit: %s\n" % self.DebugFormatInt32(self.limit_)) + if self.has_keys_only_: res+=prefix+("keys_only: %s\n" % self.DebugFormatBool(self.keys_only_)) + if self.has_entityfilter_: + res+=prefix+"EntityFilter {\n" + res+=self.entityfilter_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kPrimaryScanGroup = 1 + kPrimaryScanindex_name = 2 + kPrimaryScanstart_key = 3 + kPrimaryScanstart_inclusive = 4 + kPrimaryScanend_key = 5 + kPrimaryScanend_inclusive = 6 + kPrimaryScanend_unapplied_log_timestamp_us = 19 + kMergeJoinScanGroup = 7 + kMergeJoinScanindex_name = 8 + kMergeJoinScanprefix_value = 9 + koffset = 10 + klimit = 11 + kkeys_only = 12 + kEntityFilterGroup = 13 + kEntityFilterdistinct = 14 + kEntityFilterkind = 17 + kEntityFilterancestor = 18 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "PrimaryScan", + 2: "index_name", + 3: "start_key", + 4: "start_inclusive", + 5: "end_key", + 6: "end_inclusive", + 7: "MergeJoinScan", + 8: "index_name", + 9: "prefix_value", + 10: "offset", + 11: "limit", + 12: "keys_only", + 13: "EntityFilter", + 14: "distinct", + 17: "kind", + 18: "ancestor", + 19: "end_unapplied_log_timestamp_us", + }, 19) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.NUMERIC, + 7: ProtocolBuffer.Encoder.STARTGROUP, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.STRING, + 10: ProtocolBuffer.Encoder.NUMERIC, + 11: ProtocolBuffer.Encoder.NUMERIC, + 12: ProtocolBuffer.Encoder.NUMERIC, + 13: ProtocolBuffer.Encoder.STARTGROUP, + 14: ProtocolBuffer.Encoder.NUMERIC, + 17: ProtocolBuffer.Encoder.STRING, + 18: ProtocolBuffer.Encoder.STRING, + 19: ProtocolBuffer.Encoder.NUMERIC, + }, 19, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompiledCursor_Position(ProtocolBuffer.ProtocolMessage): + has_start_key_ = 0 + start_key_ = "" + has_start_inclusive_ = 0 + start_inclusive_ = 1 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def start_key(self): return self.start_key_ + + def set_start_key(self, x): + self.has_start_key_ = 1 + self.start_key_ = x + + def clear_start_key(self): + if self.has_start_key_: + self.has_start_key_ = 0 + self.start_key_ = "" + + def has_start_key(self): return self.has_start_key_ + + def start_inclusive(self): return self.start_inclusive_ + + def set_start_inclusive(self, x): + self.has_start_inclusive_ = 1 + self.start_inclusive_ = x + + def clear_start_inclusive(self): + if self.has_start_inclusive_: + self.has_start_inclusive_ = 0 + self.start_inclusive_ = 1 + + def has_start_inclusive(self): return self.has_start_inclusive_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_start_key()): self.set_start_key(x.start_key()) + if (x.has_start_inclusive()): self.set_start_inclusive(x.start_inclusive()) + + def Equals(self, x): + if x is self: return 1 + if self.has_start_key_ != x.has_start_key_: return 0 + if self.has_start_key_ and self.start_key_ != x.start_key_: return 0 + if self.has_start_inclusive_ != x.has_start_inclusive_: return 0 + if self.has_start_inclusive_ and self.start_inclusive_ != x.start_inclusive_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_start_key_): n += 2 + self.lengthString(len(self.start_key_)) + if (self.has_start_inclusive_): n += 3 + return n + 0 + + def Clear(self): + self.clear_start_key() + self.clear_start_inclusive() + + def OutputUnchecked(self, out): + if (self.has_start_key_): + out.putVarInt32(218) + out.putPrefixedString(self.start_key_) + if (self.has_start_inclusive_): + out.putVarInt32(224) + out.putBoolean(self.start_inclusive_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 20: break + if tt == 218: + self.set_start_key(d.getPrefixedString()) + continue + if tt == 224: + self.set_start_inclusive(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_start_key_: res+=prefix+("start_key: %s\n" % self.DebugFormatString(self.start_key_)) + if self.has_start_inclusive_: res+=prefix+("start_inclusive: %s\n" % self.DebugFormatBool(self.start_inclusive_)) + return res + +class CompiledCursor(ProtocolBuffer.ProtocolMessage): + has_multiquery_index_ = 0 + multiquery_index_ = 0 + + def __init__(self, contents=None): + self.position_ = [] + if contents is not None: self.MergeFromString(contents) + + def multiquery_index(self): return self.multiquery_index_ + + def set_multiquery_index(self, x): + self.has_multiquery_index_ = 1 + self.multiquery_index_ = x + + def clear_multiquery_index(self): + if self.has_multiquery_index_: + self.has_multiquery_index_ = 0 + self.multiquery_index_ = 0 + + def has_multiquery_index(self): return self.has_multiquery_index_ + + def position_size(self): return len(self.position_) + def position_list(self): return self.position_ + + def position(self, i): + return self.position_[i] + + def mutable_position(self, i): + return self.position_[i] + + def add_position(self): + x = CompiledCursor_Position() + self.position_.append(x) + return x + + def clear_position(self): + self.position_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_multiquery_index()): self.set_multiquery_index(x.multiquery_index()) + for i in xrange(x.position_size()): self.add_position().CopyFrom(x.position(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_multiquery_index_ != x.has_multiquery_index_: return 0 + if self.has_multiquery_index_ and self.multiquery_index_ != x.multiquery_index_: return 0 + if len(self.position_) != len(x.position_): return 0 + for e1, e2 in zip(self.position_, x.position_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.position_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_multiquery_index_): n += 1 + self.lengthVarInt64(self.multiquery_index_) + n += 2 * len(self.position_) + for i in xrange(len(self.position_)): n += self.position_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_multiquery_index() + self.clear_position() + + def OutputUnchecked(self, out): + if (self.has_multiquery_index_): + out.putVarInt32(8) + out.putVarInt32(self.multiquery_index_) + for i in xrange(len(self.position_)): + out.putVarInt32(19) + self.position_[i].OutputUnchecked(out) + out.putVarInt32(20) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_multiquery_index(d.getVarInt32()) + continue + if tt == 19: + self.add_position().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_multiquery_index_: res+=prefix+("multiquery_index: %s\n" % self.DebugFormatInt32(self.multiquery_index_)) + cnt=0 + for e in self.position_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Position%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kmultiquery_index = 1 + kPositionGroup = 2 + kPositionstart_key = 27 + kPositionstart_inclusive = 28 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "multiquery_index", + 2: "Position", + 27: "start_key", + 28: "start_inclusive", + }, 28) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STARTGROUP, + 27: ProtocolBuffer.Encoder.STRING, + 28: ProtocolBuffer.Encoder.NUMERIC, + }, 28, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class RunCompiledQueryRequest(ProtocolBuffer.ProtocolMessage): + has_app_ = 0 + app_ = "" + has_name_space_ = 0 + name_space_ = "" + has_compiled_query_ = 0 + has_original_query_ = 0 + original_query_ = None + has_count_ = 0 + count_ = 0 + has_failover_ms_ = 0 + failover_ms_ = 0 + + def __init__(self, contents=None): + self.compiled_query_ = CompiledQuery() + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def compiled_query(self): return self.compiled_query_ + + def mutable_compiled_query(self): self.has_compiled_query_ = 1; return self.compiled_query_ + + def clear_compiled_query(self):self.has_compiled_query_ = 0; self.compiled_query_.Clear() + + def has_compiled_query(self): return self.has_compiled_query_ + + def original_query(self): + if self.original_query_ is None: + self.lazy_init_lock_.acquire() + try: + if self.original_query_ is None: self.original_query_ = Query() + finally: + self.lazy_init_lock_.release() + return self.original_query_ + + def mutable_original_query(self): self.has_original_query_ = 1; return self.original_query() + + def clear_original_query(self): + if self.has_original_query_: + self.has_original_query_ = 0; + if self.original_query_ is not None: self.original_query_.Clear() + + def has_original_query(self): return self.has_original_query_ + + def count(self): return self.count_ + + def set_count(self, x): + self.has_count_ = 1 + self.count_ = x + + def clear_count(self): + if self.has_count_: + self.has_count_ = 0 + self.count_ = 0 + + def has_count(self): return self.has_count_ + + def failover_ms(self): return self.failover_ms_ + + def set_failover_ms(self, x): + self.has_failover_ms_ = 1 + self.failover_ms_ = x + + def clear_failover_ms(self): + if self.has_failover_ms_: + self.has_failover_ms_ = 0 + self.failover_ms_ = 0 + + def has_failover_ms(self): return self.has_failover_ms_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_compiled_query()): self.mutable_compiled_query().MergeFrom(x.compiled_query()) + if (x.has_original_query()): self.mutable_original_query().MergeFrom(x.original_query()) + if (x.has_count()): self.set_count(x.count()) + if (x.has_failover_ms()): self.set_failover_ms(x.failover_ms()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_compiled_query_ != x.has_compiled_query_: return 0 + if self.has_compiled_query_ and self.compiled_query_ != x.compiled_query_: return 0 + if self.has_original_query_ != x.has_original_query_: return 0 + if self.has_original_query_ and self.original_query_ != x.original_query_: return 0 + if self.has_count_ != x.has_count_: return 0 + if self.has_count_ and self.count_ != x.count_: return 0 + if self.has_failover_ms_ != x.has_failover_ms_: return 0 + if self.has_failover_ms_ and self.failover_ms_ != x.failover_ms_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app not set.') + if (not self.has_compiled_query_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: compiled_query not set.') + elif not self.compiled_query_.IsInitialized(debug_strs): initialized = 0 + if (self.has_original_query_ and not self.original_query_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_)) + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + n += self.lengthString(self.compiled_query_.ByteSize()) + if (self.has_original_query_): n += 1 + self.lengthString(self.original_query_.ByteSize()) + if (self.has_count_): n += 1 + self.lengthVarInt64(self.count_) + if (self.has_failover_ms_): n += 1 + self.lengthVarInt64(self.failover_ms_) + return n + 2 + + def Clear(self): + self.clear_app() + self.clear_name_space() + self.clear_compiled_query() + self.clear_original_query() + self.clear_count() + self.clear_failover_ms() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.compiled_query_.ByteSize()) + self.compiled_query_.OutputUnchecked(out) + if (self.has_original_query_): + out.putVarInt32(18) + out.putVarInt32(self.original_query_.ByteSize()) + self.original_query_.OutputUnchecked(out) + if (self.has_count_): + out.putVarInt32(24) + out.putVarInt32(self.count_) + if (self.has_failover_ms_): + out.putVarInt32(32) + out.putVarInt64(self.failover_ms_) + out.putVarInt32(42) + out.putPrefixedString(self.app_) + if (self.has_name_space_): + out.putVarInt32(50) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_compiled_query().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_original_query().TryMerge(tmp) + continue + if tt == 24: + self.set_count(d.getVarInt32()) + continue + if tt == 32: + self.set_failover_ms(d.getVarInt64()) + continue + if tt == 42: + self.set_app(d.getPrefixedString()) + continue + if tt == 50: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_compiled_query_: + res+=prefix+"compiled_query <\n" + res+=self.compiled_query_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_original_query_: + res+=prefix+"original_query <\n" + res+=self.original_query_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_count_: res+=prefix+("count: %s\n" % self.DebugFormatInt32(self.count_)) + if self.has_failover_ms_: res+=prefix+("failover_ms: %s\n" % self.DebugFormatInt64(self.failover_ms_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 5 + kname_space = 6 + kcompiled_query = 1 + koriginal_query = 2 + kcount = 3 + kfailover_ms = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "compiled_query", + 2: "original_query", + 3: "count", + 4: "failover_ms", + 5: "app", + 6: "name_space", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Cursor(ProtocolBuffer.ProtocolMessage): + has_cursor_ = 0 + cursor_ = 0 + has_app_ = 0 + app_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def cursor(self): return self.cursor_ + + def set_cursor(self, x): + self.has_cursor_ = 1 + self.cursor_ = x + + def clear_cursor(self): + if self.has_cursor_: + self.has_cursor_ = 0 + self.cursor_ = 0 + + def has_cursor(self): return self.has_cursor_ + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cursor()): self.set_cursor(x.cursor()) + if (x.has_app()): self.set_app(x.app()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cursor_ != x.has_cursor_: return 0 + if self.has_cursor_ and self.cursor_ != x.cursor_: return 0 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_cursor_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: cursor not set.') + return initialized + + def ByteSize(self): + n = 0 + if (self.has_app_): n += 1 + self.lengthString(len(self.app_)) + return n + 9 + + def Clear(self): + self.clear_cursor() + self.clear_app() + + def OutputUnchecked(self, out): + out.putVarInt32(9) + out.put64(self.cursor_) + if (self.has_app_): + out.putVarInt32(18) + out.putPrefixedString(self.app_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 9: + self.set_cursor(d.get64()) + continue + if tt == 18: + self.set_app(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cursor_: res+=prefix+("cursor: %s\n" % self.DebugFormatFixed64(self.cursor_)) + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcursor = 1 + kapp = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cursor", + 2: "app", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.DOUBLE, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Error(ProtocolBuffer.ProtocolMessage): + + BAD_REQUEST = 1 + CONCURRENT_TRANSACTION = 2 + INTERNAL_ERROR = 3 + NEED_INDEX = 4 + TIMEOUT = 5 + PERMISSION_DENIED = 6 + BIGTABLE_ERROR = 7 + COMMITTED_BUT_STILL_APPLYING = 8 + CAPABILITY_DISABLED = 9 + + _ErrorCode_NAMES = { + 1: "BAD_REQUEST", + 2: "CONCURRENT_TRANSACTION", + 3: "INTERNAL_ERROR", + 4: "NEED_INDEX", + 5: "TIMEOUT", + 6: "PERMISSION_DENIED", + 7: "BIGTABLE_ERROR", + 8: "COMMITTED_BUT_STILL_APPLYING", + 9: "CAPABILITY_DISABLED", + } + + def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "") + ErrorCode_Name = classmethod(ErrorCode_Name) + + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Cost(ProtocolBuffer.ProtocolMessage): + has_index_writes_ = 0 + index_writes_ = 0 + has_index_write_bytes_ = 0 + index_write_bytes_ = 0 + has_entity_writes_ = 0 + entity_writes_ = 0 + has_entity_write_bytes_ = 0 + entity_write_bytes_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def index_writes(self): return self.index_writes_ + + def set_index_writes(self, x): + self.has_index_writes_ = 1 + self.index_writes_ = x + + def clear_index_writes(self): + if self.has_index_writes_: + self.has_index_writes_ = 0 + self.index_writes_ = 0 + + def has_index_writes(self): return self.has_index_writes_ + + def index_write_bytes(self): return self.index_write_bytes_ + + def set_index_write_bytes(self, x): + self.has_index_write_bytes_ = 1 + self.index_write_bytes_ = x + + def clear_index_write_bytes(self): + if self.has_index_write_bytes_: + self.has_index_write_bytes_ = 0 + self.index_write_bytes_ = 0 + + def has_index_write_bytes(self): return self.has_index_write_bytes_ + + def entity_writes(self): return self.entity_writes_ + + def set_entity_writes(self, x): + self.has_entity_writes_ = 1 + self.entity_writes_ = x + + def clear_entity_writes(self): + if self.has_entity_writes_: + self.has_entity_writes_ = 0 + self.entity_writes_ = 0 + + def has_entity_writes(self): return self.has_entity_writes_ + + def entity_write_bytes(self): return self.entity_write_bytes_ + + def set_entity_write_bytes(self, x): + self.has_entity_write_bytes_ = 1 + self.entity_write_bytes_ = x + + def clear_entity_write_bytes(self): + if self.has_entity_write_bytes_: + self.has_entity_write_bytes_ = 0 + self.entity_write_bytes_ = 0 + + def has_entity_write_bytes(self): return self.has_entity_write_bytes_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_index_writes()): self.set_index_writes(x.index_writes()) + if (x.has_index_write_bytes()): self.set_index_write_bytes(x.index_write_bytes()) + if (x.has_entity_writes()): self.set_entity_writes(x.entity_writes()) + if (x.has_entity_write_bytes()): self.set_entity_write_bytes(x.entity_write_bytes()) + + def Equals(self, x): + if x is self: return 1 + if self.has_index_writes_ != x.has_index_writes_: return 0 + if self.has_index_writes_ and self.index_writes_ != x.index_writes_: return 0 + if self.has_index_write_bytes_ != x.has_index_write_bytes_: return 0 + if self.has_index_write_bytes_ and self.index_write_bytes_ != x.index_write_bytes_: return 0 + if self.has_entity_writes_ != x.has_entity_writes_: return 0 + if self.has_entity_writes_ and self.entity_writes_ != x.entity_writes_: return 0 + if self.has_entity_write_bytes_ != x.has_entity_write_bytes_: return 0 + if self.has_entity_write_bytes_ and self.entity_write_bytes_ != x.entity_write_bytes_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_index_writes_): n += 1 + self.lengthVarInt64(self.index_writes_) + if (self.has_index_write_bytes_): n += 1 + self.lengthVarInt64(self.index_write_bytes_) + if (self.has_entity_writes_): n += 1 + self.lengthVarInt64(self.entity_writes_) + if (self.has_entity_write_bytes_): n += 1 + self.lengthVarInt64(self.entity_write_bytes_) + return n + 0 + + def Clear(self): + self.clear_index_writes() + self.clear_index_write_bytes() + self.clear_entity_writes() + self.clear_entity_write_bytes() + + def OutputUnchecked(self, out): + if (self.has_index_writes_): + out.putVarInt32(8) + out.putVarInt32(self.index_writes_) + if (self.has_index_write_bytes_): + out.putVarInt32(16) + out.putVarInt32(self.index_write_bytes_) + if (self.has_entity_writes_): + out.putVarInt32(24) + out.putVarInt32(self.entity_writes_) + if (self.has_entity_write_bytes_): + out.putVarInt32(32) + out.putVarInt32(self.entity_write_bytes_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_index_writes(d.getVarInt32()) + continue + if tt == 16: + self.set_index_write_bytes(d.getVarInt32()) + continue + if tt == 24: + self.set_entity_writes(d.getVarInt32()) + continue + if tt == 32: + self.set_entity_write_bytes(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_index_writes_: res+=prefix+("index_writes: %s\n" % self.DebugFormatInt32(self.index_writes_)) + if self.has_index_write_bytes_: res+=prefix+("index_write_bytes: %s\n" % self.DebugFormatInt32(self.index_write_bytes_)) + if self.has_entity_writes_: res+=prefix+("entity_writes: %s\n" % self.DebugFormatInt32(self.entity_writes_)) + if self.has_entity_write_bytes_: res+=prefix+("entity_write_bytes: %s\n" % self.DebugFormatInt32(self.entity_write_bytes_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kindex_writes = 1 + kindex_write_bytes = 2 + kentity_writes = 3 + kentity_write_bytes = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "index_writes", + 2: "index_write_bytes", + 3: "entity_writes", + 4: "entity_write_bytes", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetRequest(ProtocolBuffer.ProtocolMessage): + has_transaction_ = 0 + transaction_ = None + has_failover_ms_ = 0 + failover_ms_ = 0 + has_strong_ = 0 + strong_ = 0 + + def __init__(self, contents=None): + self.key_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def key_size(self): return len(self.key_) + def key_list(self): return self.key_ + + def key(self, i): + return self.key_[i] + + def mutable_key(self, i): + return self.key_[i] + + def add_key(self): + x = Reference() + self.key_.append(x) + return x + + def clear_key(self): + self.key_ = [] + def transaction(self): + if self.transaction_ is None: + self.lazy_init_lock_.acquire() + try: + if self.transaction_ is None: self.transaction_ = Transaction() + finally: + self.lazy_init_lock_.release() + return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction() + + def clear_transaction(self): + if self.has_transaction_: + self.has_transaction_ = 0; + if self.transaction_ is not None: self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def failover_ms(self): return self.failover_ms_ + + def set_failover_ms(self, x): + self.has_failover_ms_ = 1 + self.failover_ms_ = x + + def clear_failover_ms(self): + if self.has_failover_ms_: + self.has_failover_ms_ = 0 + self.failover_ms_ = 0 + + def has_failover_ms(self): return self.has_failover_ms_ + + def strong(self): return self.strong_ + + def set_strong(self, x): + self.has_strong_ = 1 + self.strong_ = x + + def clear_strong(self): + if self.has_strong_: + self.has_strong_ = 0 + self.strong_ = 0 + + def has_strong(self): return self.has_strong_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.key_size()): self.add_key().CopyFrom(x.key(i)) + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + if (x.has_failover_ms()): self.set_failover_ms(x.failover_ms()) + if (x.has_strong()): self.set_strong(x.strong()) + + def Equals(self, x): + if x is self: return 1 + if len(self.key_) != len(x.key_): return 0 + for e1, e2 in zip(self.key_, x.key_): + if e1 != e2: return 0 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if self.has_failover_ms_ != x.has_failover_ms_: return 0 + if self.has_failover_ms_ and self.failover_ms_ != x.failover_ms_: return 0 + if self.has_strong_ != x.has_strong_: return 0 + if self.has_strong_ and self.strong_ != x.strong_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.key_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_transaction_ and not self.transaction_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.key_) + for i in xrange(len(self.key_)): n += self.lengthString(self.key_[i].ByteSize()) + if (self.has_transaction_): n += 1 + self.lengthString(self.transaction_.ByteSize()) + if (self.has_failover_ms_): n += 1 + self.lengthVarInt64(self.failover_ms_) + if (self.has_strong_): n += 2 + return n + 0 + + def Clear(self): + self.clear_key() + self.clear_transaction() + self.clear_failover_ms() + self.clear_strong() + + def OutputUnchecked(self, out): + for i in xrange(len(self.key_)): + out.putVarInt32(10) + out.putVarInt32(self.key_[i].ByteSize()) + self.key_[i].OutputUnchecked(out) + if (self.has_transaction_): + out.putVarInt32(18) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + if (self.has_failover_ms_): + out.putVarInt32(24) + out.putVarInt64(self.failover_ms_) + if (self.has_strong_): + out.putVarInt32(32) + out.putBoolean(self.strong_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_key().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 24: + self.set_failover_ms(d.getVarInt64()) + continue + if tt == 32: + self.set_strong(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("key%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_failover_ms_: res+=prefix+("failover_ms: %s\n" % self.DebugFormatInt64(self.failover_ms_)) + if self.has_strong_: res+=prefix+("strong: %s\n" % self.DebugFormatBool(self.strong_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + ktransaction = 2 + kfailover_ms = 3 + kstrong = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "transaction", + 3: "failover_ms", + 4: "strong", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetResponse_Entity(ProtocolBuffer.ProtocolMessage): + has_entity_ = 0 + entity_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def entity(self): + if self.entity_ is None: + self.lazy_init_lock_.acquire() + try: + if self.entity_ is None: self.entity_ = EntityProto() + finally: + self.lazy_init_lock_.release() + return self.entity_ + + def mutable_entity(self): self.has_entity_ = 1; return self.entity() + + def clear_entity(self): + if self.has_entity_: + self.has_entity_ = 0; + if self.entity_ is not None: self.entity_.Clear() + + def has_entity(self): return self.has_entity_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_entity()): self.mutable_entity().MergeFrom(x.entity()) + + def Equals(self, x): + if x is self: return 1 + if self.has_entity_ != x.has_entity_: return 0 + if self.has_entity_ and self.entity_ != x.entity_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_entity_ and not self.entity_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_entity_): n += 1 + self.lengthString(self.entity_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_entity() + + def OutputUnchecked(self, out): + if (self.has_entity_): + out.putVarInt32(18) + out.putVarInt32(self.entity_.ByteSize()) + self.entity_.OutputUnchecked(out) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_entity().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_entity_: + res+=prefix+"entity <\n" + res+=self.entity_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + +class GetResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.entity_ = [] + if contents is not None: self.MergeFromString(contents) + + def entity_size(self): return len(self.entity_) + def entity_list(self): return self.entity_ + + def entity(self, i): + return self.entity_[i] + + def mutable_entity(self, i): + return self.entity_[i] + + def add_entity(self): + x = GetResponse_Entity() + self.entity_.append(x) + return x + + def clear_entity(self): + self.entity_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.entity_size()): self.add_entity().CopyFrom(x.entity(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.entity_) != len(x.entity_): return 0 + for e1, e2 in zip(self.entity_, x.entity_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.entity_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.entity_) + for i in xrange(len(self.entity_)): n += self.entity_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_entity() + + def OutputUnchecked(self, out): + for i in xrange(len(self.entity_)): + out.putVarInt32(11) + self.entity_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_entity().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.entity_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Entity%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kEntityGroup = 1 + kEntityentity = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Entity", + 2: "entity", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class PutRequest(ProtocolBuffer.ProtocolMessage): + has_transaction_ = 0 + transaction_ = None + has_trusted_ = 0 + trusted_ = 0 + + def __init__(self, contents=None): + self.entity_ = [] + self.composite_index_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def entity_size(self): return len(self.entity_) + def entity_list(self): return self.entity_ + + def entity(self, i): + return self.entity_[i] + + def mutable_entity(self, i): + return self.entity_[i] + + def add_entity(self): + x = EntityProto() + self.entity_.append(x) + return x + + def clear_entity(self): + self.entity_ = [] + def transaction(self): + if self.transaction_ is None: + self.lazy_init_lock_.acquire() + try: + if self.transaction_ is None: self.transaction_ = Transaction() + finally: + self.lazy_init_lock_.release() + return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction() + + def clear_transaction(self): + if self.has_transaction_: + self.has_transaction_ = 0; + if self.transaction_ is not None: self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def composite_index_size(self): return len(self.composite_index_) + def composite_index_list(self): return self.composite_index_ + + def composite_index(self, i): + return self.composite_index_[i] + + def mutable_composite_index(self, i): + return self.composite_index_[i] + + def add_composite_index(self): + x = CompositeIndex() + self.composite_index_.append(x) + return x + + def clear_composite_index(self): + self.composite_index_ = [] + def trusted(self): return self.trusted_ + + def set_trusted(self, x): + self.has_trusted_ = 1 + self.trusted_ = x + + def clear_trusted(self): + if self.has_trusted_: + self.has_trusted_ = 0 + self.trusted_ = 0 + + def has_trusted(self): return self.has_trusted_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.entity_size()): self.add_entity().CopyFrom(x.entity(i)) + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + for i in xrange(x.composite_index_size()): self.add_composite_index().CopyFrom(x.composite_index(i)) + if (x.has_trusted()): self.set_trusted(x.trusted()) + + def Equals(self, x): + if x is self: return 1 + if len(self.entity_) != len(x.entity_): return 0 + for e1, e2 in zip(self.entity_, x.entity_): + if e1 != e2: return 0 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if len(self.composite_index_) != len(x.composite_index_): return 0 + for e1, e2 in zip(self.composite_index_, x.composite_index_): + if e1 != e2: return 0 + if self.has_trusted_ != x.has_trusted_: return 0 + if self.has_trusted_ and self.trusted_ != x.trusted_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.entity_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_transaction_ and not self.transaction_.IsInitialized(debug_strs)): initialized = 0 + for p in self.composite_index_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.entity_) + for i in xrange(len(self.entity_)): n += self.lengthString(self.entity_[i].ByteSize()) + if (self.has_transaction_): n += 1 + self.lengthString(self.transaction_.ByteSize()) + n += 1 * len(self.composite_index_) + for i in xrange(len(self.composite_index_)): n += self.lengthString(self.composite_index_[i].ByteSize()) + if (self.has_trusted_): n += 2 + return n + 0 + + def Clear(self): + self.clear_entity() + self.clear_transaction() + self.clear_composite_index() + self.clear_trusted() + + def OutputUnchecked(self, out): + for i in xrange(len(self.entity_)): + out.putVarInt32(10) + out.putVarInt32(self.entity_[i].ByteSize()) + self.entity_[i].OutputUnchecked(out) + if (self.has_transaction_): + out.putVarInt32(18) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + for i in xrange(len(self.composite_index_)): + out.putVarInt32(26) + out.putVarInt32(self.composite_index_[i].ByteSize()) + self.composite_index_[i].OutputUnchecked(out) + if (self.has_trusted_): + out.putVarInt32(32) + out.putBoolean(self.trusted_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_entity().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_composite_index().TryMerge(tmp) + continue + if tt == 32: + self.set_trusted(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.entity_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("entity%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.composite_index_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("composite_index%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_trusted_: res+=prefix+("trusted: %s\n" % self.DebugFormatBool(self.trusted_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kentity = 1 + ktransaction = 2 + kcomposite_index = 3 + ktrusted = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "entity", + 2: "transaction", + 3: "composite_index", + 4: "trusted", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class PutResponse(ProtocolBuffer.ProtocolMessage): + has_cost_ = 0 + cost_ = None + + def __init__(self, contents=None): + self.key_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def key_size(self): return len(self.key_) + def key_list(self): return self.key_ + + def key(self, i): + return self.key_[i] + + def mutable_key(self, i): + return self.key_[i] + + def add_key(self): + x = Reference() + self.key_.append(x) + return x + + def clear_key(self): + self.key_ = [] + def cost(self): + if self.cost_ is None: + self.lazy_init_lock_.acquire() + try: + if self.cost_ is None: self.cost_ = Cost() + finally: + self.lazy_init_lock_.release() + return self.cost_ + + def mutable_cost(self): self.has_cost_ = 1; return self.cost() + + def clear_cost(self): + if self.has_cost_: + self.has_cost_ = 0; + if self.cost_ is not None: self.cost_.Clear() + + def has_cost(self): return self.has_cost_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.key_size()): self.add_key().CopyFrom(x.key(i)) + if (x.has_cost()): self.mutable_cost().MergeFrom(x.cost()) + + def Equals(self, x): + if x is self: return 1 + if len(self.key_) != len(x.key_): return 0 + for e1, e2 in zip(self.key_, x.key_): + if e1 != e2: return 0 + if self.has_cost_ != x.has_cost_: return 0 + if self.has_cost_ and self.cost_ != x.cost_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.key_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_cost_ and not self.cost_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.key_) + for i in xrange(len(self.key_)): n += self.lengthString(self.key_[i].ByteSize()) + if (self.has_cost_): n += 1 + self.lengthString(self.cost_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_key() + self.clear_cost() + + def OutputUnchecked(self, out): + for i in xrange(len(self.key_)): + out.putVarInt32(10) + out.putVarInt32(self.key_[i].ByteSize()) + self.key_[i].OutputUnchecked(out) + if (self.has_cost_): + out.putVarInt32(18) + out.putVarInt32(self.cost_.ByteSize()) + self.cost_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_key().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cost().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("key%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_cost_: + res+=prefix+"cost <\n" + res+=self.cost_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + kcost = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "cost", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TouchRequest(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.key_ = [] + self.composite_index_ = [] + if contents is not None: self.MergeFromString(contents) + + def key_size(self): return len(self.key_) + def key_list(self): return self.key_ + + def key(self, i): + return self.key_[i] + + def mutable_key(self, i): + return self.key_[i] + + def add_key(self): + x = Reference() + self.key_.append(x) + return x + + def clear_key(self): + self.key_ = [] + def composite_index_size(self): return len(self.composite_index_) + def composite_index_list(self): return self.composite_index_ + + def composite_index(self, i): + return self.composite_index_[i] + + def mutable_composite_index(self, i): + return self.composite_index_[i] + + def add_composite_index(self): + x = CompositeIndex() + self.composite_index_.append(x) + return x + + def clear_composite_index(self): + self.composite_index_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.key_size()): self.add_key().CopyFrom(x.key(i)) + for i in xrange(x.composite_index_size()): self.add_composite_index().CopyFrom(x.composite_index(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.key_) != len(x.key_): return 0 + for e1, e2 in zip(self.key_, x.key_): + if e1 != e2: return 0 + if len(self.composite_index_) != len(x.composite_index_): return 0 + for e1, e2 in zip(self.composite_index_, x.composite_index_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.key_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.composite_index_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.key_) + for i in xrange(len(self.key_)): n += self.lengthString(self.key_[i].ByteSize()) + n += 1 * len(self.composite_index_) + for i in xrange(len(self.composite_index_)): n += self.lengthString(self.composite_index_[i].ByteSize()) + return n + 0 + + def Clear(self): + self.clear_key() + self.clear_composite_index() + + def OutputUnchecked(self, out): + for i in xrange(len(self.key_)): + out.putVarInt32(10) + out.putVarInt32(self.key_[i].ByteSize()) + self.key_[i].OutputUnchecked(out) + for i in xrange(len(self.composite_index_)): + out.putVarInt32(18) + out.putVarInt32(self.composite_index_[i].ByteSize()) + self.composite_index_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_key().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_composite_index().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("key%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + cnt=0 + for e in self.composite_index_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("composite_index%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + kcomposite_index = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "composite_index", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TouchResponse(ProtocolBuffer.ProtocolMessage): + has_cost_ = 0 + cost_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def cost(self): + if self.cost_ is None: + self.lazy_init_lock_.acquire() + try: + if self.cost_ is None: self.cost_ = Cost() + finally: + self.lazy_init_lock_.release() + return self.cost_ + + def mutable_cost(self): self.has_cost_ = 1; return self.cost() + + def clear_cost(self): + if self.has_cost_: + self.has_cost_ = 0; + if self.cost_ is not None: self.cost_.Clear() + + def has_cost(self): return self.has_cost_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cost()): self.mutable_cost().MergeFrom(x.cost()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cost_ != x.has_cost_: return 0 + if self.has_cost_ and self.cost_ != x.cost_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_cost_ and not self.cost_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_cost_): n += 1 + self.lengthString(self.cost_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_cost() + + def OutputUnchecked(self, out): + if (self.has_cost_): + out.putVarInt32(10) + out.putVarInt32(self.cost_.ByteSize()) + self.cost_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cost().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cost_: + res+=prefix+"cost <\n" + res+=self.cost_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcost = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cost", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DeleteRequest(ProtocolBuffer.ProtocolMessage): + has_transaction_ = 0 + transaction_ = None + has_trusted_ = 0 + trusted_ = 0 + + def __init__(self, contents=None): + self.key_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def key_size(self): return len(self.key_) + def key_list(self): return self.key_ + + def key(self, i): + return self.key_[i] + + def mutable_key(self, i): + return self.key_[i] + + def add_key(self): + x = Reference() + self.key_.append(x) + return x + + def clear_key(self): + self.key_ = [] + def transaction(self): + if self.transaction_ is None: + self.lazy_init_lock_.acquire() + try: + if self.transaction_ is None: self.transaction_ = Transaction() + finally: + self.lazy_init_lock_.release() + return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction() + + def clear_transaction(self): + if self.has_transaction_: + self.has_transaction_ = 0; + if self.transaction_ is not None: self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def trusted(self): return self.trusted_ + + def set_trusted(self, x): + self.has_trusted_ = 1 + self.trusted_ = x + + def clear_trusted(self): + if self.has_trusted_: + self.has_trusted_ = 0 + self.trusted_ = 0 + + def has_trusted(self): return self.has_trusted_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.key_size()): self.add_key().CopyFrom(x.key(i)) + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + if (x.has_trusted()): self.set_trusted(x.trusted()) + + def Equals(self, x): + if x is self: return 1 + if len(self.key_) != len(x.key_): return 0 + for e1, e2 in zip(self.key_, x.key_): + if e1 != e2: return 0 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if self.has_trusted_ != x.has_trusted_: return 0 + if self.has_trusted_ and self.trusted_ != x.trusted_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.key_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_transaction_ and not self.transaction_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.key_) + for i in xrange(len(self.key_)): n += self.lengthString(self.key_[i].ByteSize()) + if (self.has_transaction_): n += 1 + self.lengthString(self.transaction_.ByteSize()) + if (self.has_trusted_): n += 2 + return n + 0 + + def Clear(self): + self.clear_key() + self.clear_transaction() + self.clear_trusted() + + def OutputUnchecked(self, out): + if (self.has_trusted_): + out.putVarInt32(32) + out.putBoolean(self.trusted_) + if (self.has_transaction_): + out.putVarInt32(42) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + for i in xrange(len(self.key_)): + out.putVarInt32(50) + out.putVarInt32(self.key_[i].ByteSize()) + self.key_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 32: + self.set_trusted(d.getBoolean()) + continue + if tt == 42: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 50: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_key().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.key_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("key%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_trusted_: res+=prefix+("trusted: %s\n" % self.DebugFormatBool(self.trusted_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 6 + ktransaction = 5 + ktrusted = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 4: "trusted", + 5: "transaction", + 6: "key", + }, 6) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + }, 6, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class DeleteResponse(ProtocolBuffer.ProtocolMessage): + has_cost_ = 0 + cost_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def cost(self): + if self.cost_ is None: + self.lazy_init_lock_.acquire() + try: + if self.cost_ is None: self.cost_ = Cost() + finally: + self.lazy_init_lock_.release() + return self.cost_ + + def mutable_cost(self): self.has_cost_ = 1; return self.cost() + + def clear_cost(self): + if self.has_cost_: + self.has_cost_ = 0; + if self.cost_ is not None: self.cost_.Clear() + + def has_cost(self): return self.has_cost_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cost()): self.mutable_cost().MergeFrom(x.cost()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cost_ != x.has_cost_: return 0 + if self.has_cost_ and self.cost_ != x.cost_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_cost_ and not self.cost_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_cost_): n += 1 + self.lengthString(self.cost_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_cost() + + def OutputUnchecked(self, out): + if (self.has_cost_): + out.putVarInt32(10) + out.putVarInt32(self.cost_.ByteSize()) + self.cost_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cost().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cost_: + res+=prefix+"cost <\n" + res+=self.cost_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcost = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cost", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class NextRequest(ProtocolBuffer.ProtocolMessage): + has_cursor_ = 0 + has_count_ = 0 + count_ = 0 + has_offset_ = 0 + offset_ = 0 + has_compile_ = 0 + compile_ = 0 + + def __init__(self, contents=None): + self.cursor_ = Cursor() + if contents is not None: self.MergeFromString(contents) + + def cursor(self): return self.cursor_ + + def mutable_cursor(self): self.has_cursor_ = 1; return self.cursor_ + + def clear_cursor(self):self.has_cursor_ = 0; self.cursor_.Clear() + + def has_cursor(self): return self.has_cursor_ + + def count(self): return self.count_ + + def set_count(self, x): + self.has_count_ = 1 + self.count_ = x + + def clear_count(self): + if self.has_count_: + self.has_count_ = 0 + self.count_ = 0 + + def has_count(self): return self.has_count_ + + def offset(self): return self.offset_ + + def set_offset(self, x): + self.has_offset_ = 1 + self.offset_ = x + + def clear_offset(self): + if self.has_offset_: + self.has_offset_ = 0 + self.offset_ = 0 + + def has_offset(self): return self.has_offset_ + + def compile(self): return self.compile_ + + def set_compile(self, x): + self.has_compile_ = 1 + self.compile_ = x + + def clear_compile(self): + if self.has_compile_: + self.has_compile_ = 0 + self.compile_ = 0 + + def has_compile(self): return self.has_compile_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cursor()): self.mutable_cursor().MergeFrom(x.cursor()) + if (x.has_count()): self.set_count(x.count()) + if (x.has_offset()): self.set_offset(x.offset()) + if (x.has_compile()): self.set_compile(x.compile()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cursor_ != x.has_cursor_: return 0 + if self.has_cursor_ and self.cursor_ != x.cursor_: return 0 + if self.has_count_ != x.has_count_: return 0 + if self.has_count_ and self.count_ != x.count_: return 0 + if self.has_offset_ != x.has_offset_: return 0 + if self.has_offset_ and self.offset_ != x.offset_: return 0 + if self.has_compile_ != x.has_compile_: return 0 + if self.has_compile_ and self.compile_ != x.compile_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_cursor_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: cursor not set.') + elif not self.cursor_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.cursor_.ByteSize()) + if (self.has_count_): n += 1 + self.lengthVarInt64(self.count_) + if (self.has_offset_): n += 1 + self.lengthVarInt64(self.offset_) + if (self.has_compile_): n += 2 + return n + 1 + + def Clear(self): + self.clear_cursor() + self.clear_count() + self.clear_offset() + self.clear_compile() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.cursor_.ByteSize()) + self.cursor_.OutputUnchecked(out) + if (self.has_count_): + out.putVarInt32(16) + out.putVarInt32(self.count_) + if (self.has_compile_): + out.putVarInt32(24) + out.putBoolean(self.compile_) + if (self.has_offset_): + out.putVarInt32(32) + out.putVarInt32(self.offset_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cursor().TryMerge(tmp) + continue + if tt == 16: + self.set_count(d.getVarInt32()) + continue + if tt == 24: + self.set_compile(d.getBoolean()) + continue + if tt == 32: + self.set_offset(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cursor_: + res+=prefix+"cursor <\n" + res+=self.cursor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_count_: res+=prefix+("count: %s\n" % self.DebugFormatInt32(self.count_)) + if self.has_offset_: res+=prefix+("offset: %s\n" % self.DebugFormatInt32(self.offset_)) + if self.has_compile_: res+=prefix+("compile: %s\n" % self.DebugFormatBool(self.compile_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcursor = 1 + kcount = 2 + koffset = 4 + kcompile = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cursor", + 2: "count", + 3: "compile", + 4: "offset", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class QueryResult(ProtocolBuffer.ProtocolMessage): + has_cursor_ = 0 + cursor_ = None + has_skipped_results_ = 0 + skipped_results_ = 0 + has_more_results_ = 0 + more_results_ = 0 + has_keys_only_ = 0 + keys_only_ = 0 + has_compiled_query_ = 0 + compiled_query_ = None + has_compiled_cursor_ = 0 + compiled_cursor_ = None + + def __init__(self, contents=None): + self.result_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def cursor(self): + if self.cursor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.cursor_ is None: self.cursor_ = Cursor() + finally: + self.lazy_init_lock_.release() + return self.cursor_ + + def mutable_cursor(self): self.has_cursor_ = 1; return self.cursor() + + def clear_cursor(self): + if self.has_cursor_: + self.has_cursor_ = 0; + if self.cursor_ is not None: self.cursor_.Clear() + + def has_cursor(self): return self.has_cursor_ + + def result_size(self): return len(self.result_) + def result_list(self): return self.result_ + + def result(self, i): + return self.result_[i] + + def mutable_result(self, i): + return self.result_[i] + + def add_result(self): + x = EntityProto() + self.result_.append(x) + return x + + def clear_result(self): + self.result_ = [] + def skipped_results(self): return self.skipped_results_ + + def set_skipped_results(self, x): + self.has_skipped_results_ = 1 + self.skipped_results_ = x + + def clear_skipped_results(self): + if self.has_skipped_results_: + self.has_skipped_results_ = 0 + self.skipped_results_ = 0 + + def has_skipped_results(self): return self.has_skipped_results_ + + def more_results(self): return self.more_results_ + + def set_more_results(self, x): + self.has_more_results_ = 1 + self.more_results_ = x + + def clear_more_results(self): + if self.has_more_results_: + self.has_more_results_ = 0 + self.more_results_ = 0 + + def has_more_results(self): return self.has_more_results_ + + def keys_only(self): return self.keys_only_ + + def set_keys_only(self, x): + self.has_keys_only_ = 1 + self.keys_only_ = x + + def clear_keys_only(self): + if self.has_keys_only_: + self.has_keys_only_ = 0 + self.keys_only_ = 0 + + def has_keys_only(self): return self.has_keys_only_ + + def compiled_query(self): + if self.compiled_query_ is None: + self.lazy_init_lock_.acquire() + try: + if self.compiled_query_ is None: self.compiled_query_ = CompiledQuery() + finally: + self.lazy_init_lock_.release() + return self.compiled_query_ + + def mutable_compiled_query(self): self.has_compiled_query_ = 1; return self.compiled_query() + + def clear_compiled_query(self): + if self.has_compiled_query_: + self.has_compiled_query_ = 0; + if self.compiled_query_ is not None: self.compiled_query_.Clear() + + def has_compiled_query(self): return self.has_compiled_query_ + + def compiled_cursor(self): + if self.compiled_cursor_ is None: + self.lazy_init_lock_.acquire() + try: + if self.compiled_cursor_ is None: self.compiled_cursor_ = CompiledCursor() + finally: + self.lazy_init_lock_.release() + return self.compiled_cursor_ + + def mutable_compiled_cursor(self): self.has_compiled_cursor_ = 1; return self.compiled_cursor() + + def clear_compiled_cursor(self): + if self.has_compiled_cursor_: + self.has_compiled_cursor_ = 0; + if self.compiled_cursor_ is not None: self.compiled_cursor_.Clear() + + def has_compiled_cursor(self): return self.has_compiled_cursor_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cursor()): self.mutable_cursor().MergeFrom(x.cursor()) + for i in xrange(x.result_size()): self.add_result().CopyFrom(x.result(i)) + if (x.has_skipped_results()): self.set_skipped_results(x.skipped_results()) + if (x.has_more_results()): self.set_more_results(x.more_results()) + if (x.has_keys_only()): self.set_keys_only(x.keys_only()) + if (x.has_compiled_query()): self.mutable_compiled_query().MergeFrom(x.compiled_query()) + if (x.has_compiled_cursor()): self.mutable_compiled_cursor().MergeFrom(x.compiled_cursor()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cursor_ != x.has_cursor_: return 0 + if self.has_cursor_ and self.cursor_ != x.cursor_: return 0 + if len(self.result_) != len(x.result_): return 0 + for e1, e2 in zip(self.result_, x.result_): + if e1 != e2: return 0 + if self.has_skipped_results_ != x.has_skipped_results_: return 0 + if self.has_skipped_results_ and self.skipped_results_ != x.skipped_results_: return 0 + if self.has_more_results_ != x.has_more_results_: return 0 + if self.has_more_results_ and self.more_results_ != x.more_results_: return 0 + if self.has_keys_only_ != x.has_keys_only_: return 0 + if self.has_keys_only_ and self.keys_only_ != x.keys_only_: return 0 + if self.has_compiled_query_ != x.has_compiled_query_: return 0 + if self.has_compiled_query_ and self.compiled_query_ != x.compiled_query_: return 0 + if self.has_compiled_cursor_ != x.has_compiled_cursor_: return 0 + if self.has_compiled_cursor_ and self.compiled_cursor_ != x.compiled_cursor_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_cursor_ and not self.cursor_.IsInitialized(debug_strs)): initialized = 0 + for p in self.result_: + if not p.IsInitialized(debug_strs): initialized=0 + if (not self.has_more_results_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: more_results not set.') + if (self.has_compiled_query_ and not self.compiled_query_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_compiled_cursor_ and not self.compiled_cursor_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_cursor_): n += 1 + self.lengthString(self.cursor_.ByteSize()) + n += 1 * len(self.result_) + for i in xrange(len(self.result_)): n += self.lengthString(self.result_[i].ByteSize()) + if (self.has_skipped_results_): n += 1 + self.lengthVarInt64(self.skipped_results_) + if (self.has_keys_only_): n += 2 + if (self.has_compiled_query_): n += 1 + self.lengthString(self.compiled_query_.ByteSize()) + if (self.has_compiled_cursor_): n += 1 + self.lengthString(self.compiled_cursor_.ByteSize()) + return n + 2 + + def Clear(self): + self.clear_cursor() + self.clear_result() + self.clear_skipped_results() + self.clear_more_results() + self.clear_keys_only() + self.clear_compiled_query() + self.clear_compiled_cursor() + + def OutputUnchecked(self, out): + if (self.has_cursor_): + out.putVarInt32(10) + out.putVarInt32(self.cursor_.ByteSize()) + self.cursor_.OutputUnchecked(out) + for i in xrange(len(self.result_)): + out.putVarInt32(18) + out.putVarInt32(self.result_[i].ByteSize()) + self.result_[i].OutputUnchecked(out) + out.putVarInt32(24) + out.putBoolean(self.more_results_) + if (self.has_keys_only_): + out.putVarInt32(32) + out.putBoolean(self.keys_only_) + if (self.has_compiled_query_): + out.putVarInt32(42) + out.putVarInt32(self.compiled_query_.ByteSize()) + self.compiled_query_.OutputUnchecked(out) + if (self.has_compiled_cursor_): + out.putVarInt32(50) + out.putVarInt32(self.compiled_cursor_.ByteSize()) + self.compiled_cursor_.OutputUnchecked(out) + if (self.has_skipped_results_): + out.putVarInt32(56) + out.putVarInt32(self.skipped_results_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cursor().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_result().TryMerge(tmp) + continue + if tt == 24: + self.set_more_results(d.getBoolean()) + continue + if tt == 32: + self.set_keys_only(d.getBoolean()) + continue + if tt == 42: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_compiled_query().TryMerge(tmp) + continue + if tt == 50: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_compiled_cursor().TryMerge(tmp) + continue + if tt == 56: + self.set_skipped_results(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cursor_: + res+=prefix+"cursor <\n" + res+=self.cursor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.result_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("result%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_skipped_results_: res+=prefix+("skipped_results: %s\n" % self.DebugFormatInt32(self.skipped_results_)) + if self.has_more_results_: res+=prefix+("more_results: %s\n" % self.DebugFormatBool(self.more_results_)) + if self.has_keys_only_: res+=prefix+("keys_only: %s\n" % self.DebugFormatBool(self.keys_only_)) + if self.has_compiled_query_: + res+=prefix+"compiled_query <\n" + res+=self.compiled_query_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_compiled_cursor_: + res+=prefix+"compiled_cursor <\n" + res+=self.compiled_cursor_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcursor = 1 + kresult = 2 + kskipped_results = 7 + kmore_results = 3 + kkeys_only = 4 + kcompiled_query = 5 + kcompiled_cursor = 6 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cursor", + 2: "result", + 3: "more_results", + 4: "keys_only", + 5: "compiled_query", + 6: "compiled_cursor", + 7: "skipped_results", + }, 7) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + 7: ProtocolBuffer.Encoder.NUMERIC, + }, 7, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetSchemaRequest(ProtocolBuffer.ProtocolMessage): + has_app_ = 0 + app_ = "" + has_name_space_ = 0 + name_space_ = "" + has_start_kind_ = 0 + start_kind_ = "" + has_end_kind_ = 0 + end_kind_ = "" + has_properties_ = 0 + properties_ = 1 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def start_kind(self): return self.start_kind_ + + def set_start_kind(self, x): + self.has_start_kind_ = 1 + self.start_kind_ = x + + def clear_start_kind(self): + if self.has_start_kind_: + self.has_start_kind_ = 0 + self.start_kind_ = "" + + def has_start_kind(self): return self.has_start_kind_ + + def end_kind(self): return self.end_kind_ + + def set_end_kind(self, x): + self.has_end_kind_ = 1 + self.end_kind_ = x + + def clear_end_kind(self): + if self.has_end_kind_: + self.has_end_kind_ = 0 + self.end_kind_ = "" + + def has_end_kind(self): return self.has_end_kind_ + + def properties(self): return self.properties_ + + def set_properties(self, x): + self.has_properties_ = 1 + self.properties_ = x + + def clear_properties(self): + if self.has_properties_: + self.has_properties_ = 0 + self.properties_ = 1 + + def has_properties(self): return self.has_properties_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_start_kind()): self.set_start_kind(x.start_kind()) + if (x.has_end_kind()): self.set_end_kind(x.end_kind()) + if (x.has_properties()): self.set_properties(x.properties()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_start_kind_ != x.has_start_kind_: return 0 + if self.has_start_kind_ and self.start_kind_ != x.start_kind_: return 0 + if self.has_end_kind_ != x.has_end_kind_: return 0 + if self.has_end_kind_ and self.end_kind_ != x.end_kind_: return 0 + if self.has_properties_ != x.has_properties_: return 0 + if self.has_properties_ and self.properties_ != x.properties_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_)) + if (self.has_name_space_): n += 1 + self.lengthString(len(self.name_space_)) + if (self.has_start_kind_): n += 1 + self.lengthString(len(self.start_kind_)) + if (self.has_end_kind_): n += 1 + self.lengthString(len(self.end_kind_)) + if (self.has_properties_): n += 2 + return n + 1 + + def Clear(self): + self.clear_app() + self.clear_name_space() + self.clear_start_kind() + self.clear_end_kind() + self.clear_properties() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_) + if (self.has_start_kind_): + out.putVarInt32(18) + out.putPrefixedString(self.start_kind_) + if (self.has_end_kind_): + out.putVarInt32(26) + out.putPrefixedString(self.end_kind_) + if (self.has_properties_): + out.putVarInt32(32) + out.putBoolean(self.properties_) + if (self.has_name_space_): + out.putVarInt32(42) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app(d.getPrefixedString()) + continue + if tt == 18: + self.set_start_kind(d.getPrefixedString()) + continue + if tt == 26: + self.set_end_kind(d.getPrefixedString()) + continue + if tt == 32: + self.set_properties(d.getBoolean()) + continue + if tt == 42: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_start_kind_: res+=prefix+("start_kind: %s\n" % self.DebugFormatString(self.start_kind_)) + if self.has_end_kind_: res+=prefix+("end_kind: %s\n" % self.DebugFormatString(self.end_kind_)) + if self.has_properties_: res+=prefix+("properties: %s\n" % self.DebugFormatBool(self.properties_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 1 + kname_space = 5 + kstart_kind = 2 + kend_kind = 3 + kproperties = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app", + 2: "start_kind", + 3: "end_kind", + 4: "properties", + 5: "name_space", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Schema(ProtocolBuffer.ProtocolMessage): + has_more_results_ = 0 + more_results_ = 0 + + def __init__(self, contents=None): + self.kind_ = [] + if contents is not None: self.MergeFromString(contents) + + def kind_size(self): return len(self.kind_) + def kind_list(self): return self.kind_ + + def kind(self, i): + return self.kind_[i] + + def mutable_kind(self, i): + return self.kind_[i] + + def add_kind(self): + x = EntityProto() + self.kind_.append(x) + return x + + def clear_kind(self): + self.kind_ = [] + def more_results(self): return self.more_results_ + + def set_more_results(self, x): + self.has_more_results_ = 1 + self.more_results_ = x + + def clear_more_results(self): + if self.has_more_results_: + self.has_more_results_ = 0 + self.more_results_ = 0 + + def has_more_results(self): return self.has_more_results_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.kind_size()): self.add_kind().CopyFrom(x.kind(i)) + if (x.has_more_results()): self.set_more_results(x.more_results()) + + def Equals(self, x): + if x is self: return 1 + if len(self.kind_) != len(x.kind_): return 0 + for e1, e2 in zip(self.kind_, x.kind_): + if e1 != e2: return 0 + if self.has_more_results_ != x.has_more_results_: return 0 + if self.has_more_results_ and self.more_results_ != x.more_results_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.kind_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.kind_) + for i in xrange(len(self.kind_)): n += self.lengthString(self.kind_[i].ByteSize()) + if (self.has_more_results_): n += 2 + return n + 0 + + def Clear(self): + self.clear_kind() + self.clear_more_results() + + def OutputUnchecked(self, out): + for i in xrange(len(self.kind_)): + out.putVarInt32(10) + out.putVarInt32(self.kind_[i].ByteSize()) + self.kind_[i].OutputUnchecked(out) + if (self.has_more_results_): + out.putVarInt32(16) + out.putBoolean(self.more_results_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_kind().TryMerge(tmp) + continue + if tt == 16: + self.set_more_results(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.kind_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("kind%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_more_results_: res+=prefix+("more_results: %s\n" % self.DebugFormatBool(self.more_results_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkind = 1 + kmore_results = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "kind", + 2: "more_results", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetNamespacesRequest(ProtocolBuffer.ProtocolMessage): + has_app_ = 0 + app_ = "" + has_start_namespace_ = 0 + start_namespace_ = "" + has_end_namespace_ = 0 + end_namespace_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + def start_namespace(self): return self.start_namespace_ + + def set_start_namespace(self, x): + self.has_start_namespace_ = 1 + self.start_namespace_ = x + + def clear_start_namespace(self): + if self.has_start_namespace_: + self.has_start_namespace_ = 0 + self.start_namespace_ = "" + + def has_start_namespace(self): return self.has_start_namespace_ + + def end_namespace(self): return self.end_namespace_ + + def set_end_namespace(self, x): + self.has_end_namespace_ = 1 + self.end_namespace_ = x + + def clear_end_namespace(self): + if self.has_end_namespace_: + self.has_end_namespace_ = 0 + self.end_namespace_ = "" + + def has_end_namespace(self): return self.has_end_namespace_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + if (x.has_start_namespace()): self.set_start_namespace(x.start_namespace()) + if (x.has_end_namespace()): self.set_end_namespace(x.end_namespace()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + if self.has_start_namespace_ != x.has_start_namespace_: return 0 + if self.has_start_namespace_ and self.start_namespace_ != x.start_namespace_: return 0 + if self.has_end_namespace_ != x.has_end_namespace_: return 0 + if self.has_end_namespace_ and self.end_namespace_ != x.end_namespace_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_)) + if (self.has_start_namespace_): n += 1 + self.lengthString(len(self.start_namespace_)) + if (self.has_end_namespace_): n += 1 + self.lengthString(len(self.end_namespace_)) + return n + 1 + + def Clear(self): + self.clear_app() + self.clear_start_namespace() + self.clear_end_namespace() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_) + if (self.has_start_namespace_): + out.putVarInt32(18) + out.putPrefixedString(self.start_namespace_) + if (self.has_end_namespace_): + out.putVarInt32(26) + out.putPrefixedString(self.end_namespace_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app(d.getPrefixedString()) + continue + if tt == 18: + self.set_start_namespace(d.getPrefixedString()) + continue + if tt == 26: + self.set_end_namespace(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + if self.has_start_namespace_: res+=prefix+("start_namespace: %s\n" % self.DebugFormatString(self.start_namespace_)) + if self.has_end_namespace_: res+=prefix+("end_namespace: %s\n" % self.DebugFormatString(self.end_namespace_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 1 + kstart_namespace = 2 + kend_namespace = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app", + 2: "start_namespace", + 3: "end_namespace", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class GetNamespacesResponse(ProtocolBuffer.ProtocolMessage): + has_more_results_ = 0 + more_results_ = 0 + + def __init__(self, contents=None): + self.namespace_ = [] + if contents is not None: self.MergeFromString(contents) + + def namespace_size(self): return len(self.namespace_) + def namespace_list(self): return self.namespace_ + + def namespace(self, i): + return self.namespace_[i] + + def set_namespace(self, i, x): + self.namespace_[i] = x + + def add_namespace(self, x): + self.namespace_.append(x) + + def clear_namespace(self): + self.namespace_ = [] + + def more_results(self): return self.more_results_ + + def set_more_results(self, x): + self.has_more_results_ = 1 + self.more_results_ = x + + def clear_more_results(self): + if self.has_more_results_: + self.has_more_results_ = 0 + self.more_results_ = 0 + + def has_more_results(self): return self.has_more_results_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.namespace_size()): self.add_namespace(x.namespace(i)) + if (x.has_more_results()): self.set_more_results(x.more_results()) + + def Equals(self, x): + if x is self: return 1 + if len(self.namespace_) != len(x.namespace_): return 0 + for e1, e2 in zip(self.namespace_, x.namespace_): + if e1 != e2: return 0 + if self.has_more_results_ != x.has_more_results_: return 0 + if self.has_more_results_ and self.more_results_ != x.more_results_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.namespace_) + for i in xrange(len(self.namespace_)): n += self.lengthString(len(self.namespace_[i])) + if (self.has_more_results_): n += 2 + return n + 0 + + def Clear(self): + self.clear_namespace() + self.clear_more_results() + + def OutputUnchecked(self, out): + for i in xrange(len(self.namespace_)): + out.putVarInt32(10) + out.putPrefixedString(self.namespace_[i]) + if (self.has_more_results_): + out.putVarInt32(16) + out.putBoolean(self.more_results_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.add_namespace(d.getPrefixedString()) + continue + if tt == 16: + self.set_more_results(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.namespace_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("namespace%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + if self.has_more_results_: res+=prefix+("more_results: %s\n" % self.DebugFormatBool(self.more_results_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + knamespace = 1 + kmore_results = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "namespace", + 2: "more_results", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class AllocateIdsRequest(ProtocolBuffer.ProtocolMessage): + has_model_key_ = 0 + has_size_ = 0 + size_ = 0 + has_max_ = 0 + max_ = 0 + + def __init__(self, contents=None): + self.model_key_ = Reference() + if contents is not None: self.MergeFromString(contents) + + def model_key(self): return self.model_key_ + + def mutable_model_key(self): self.has_model_key_ = 1; return self.model_key_ + + def clear_model_key(self):self.has_model_key_ = 0; self.model_key_.Clear() + + def has_model_key(self): return self.has_model_key_ + + def size(self): return self.size_ + + def set_size(self, x): + self.has_size_ = 1 + self.size_ = x + + def clear_size(self): + if self.has_size_: + self.has_size_ = 0 + self.size_ = 0 + + def has_size(self): return self.has_size_ + + def max(self): return self.max_ + + def set_max(self, x): + self.has_max_ = 1 + self.max_ = x + + def clear_max(self): + if self.has_max_: + self.has_max_ = 0 + self.max_ = 0 + + def has_max(self): return self.has_max_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_model_key()): self.mutable_model_key().MergeFrom(x.model_key()) + if (x.has_size()): self.set_size(x.size()) + if (x.has_max()): self.set_max(x.max()) + + def Equals(self, x): + if x is self: return 1 + if self.has_model_key_ != x.has_model_key_: return 0 + if self.has_model_key_ and self.model_key_ != x.model_key_: return 0 + if self.has_size_ != x.has_size_: return 0 + if self.has_size_ and self.size_ != x.size_: return 0 + if self.has_max_ != x.has_max_: return 0 + if self.has_max_ and self.max_ != x.max_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_model_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: model_key not set.') + elif not self.model_key_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.model_key_.ByteSize()) + if (self.has_size_): n += 1 + self.lengthVarInt64(self.size_) + if (self.has_max_): n += 1 + self.lengthVarInt64(self.max_) + return n + 1 + + def Clear(self): + self.clear_model_key() + self.clear_size() + self.clear_max() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.model_key_.ByteSize()) + self.model_key_.OutputUnchecked(out) + if (self.has_size_): + out.putVarInt32(16) + out.putVarInt64(self.size_) + if (self.has_max_): + out.putVarInt32(24) + out.putVarInt64(self.max_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_model_key().TryMerge(tmp) + continue + if tt == 16: + self.set_size(d.getVarInt64()) + continue + if tt == 24: + self.set_max(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_model_key_: + res+=prefix+"model_key <\n" + res+=self.model_key_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_size_: res+=prefix+("size: %s\n" % self.DebugFormatInt64(self.size_)) + if self.has_max_: res+=prefix+("max: %s\n" % self.DebugFormatInt64(self.max_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kmodel_key = 1 + ksize = 2 + kmax = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "model_key", + 2: "size", + 3: "max", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class AllocateIdsResponse(ProtocolBuffer.ProtocolMessage): + has_start_ = 0 + start_ = 0 + has_end_ = 0 + end_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def start(self): return self.start_ + + def set_start(self, x): + self.has_start_ = 1 + self.start_ = x + + def clear_start(self): + if self.has_start_: + self.has_start_ = 0 + self.start_ = 0 + + def has_start(self): return self.has_start_ + + def end(self): return self.end_ + + def set_end(self, x): + self.has_end_ = 1 + self.end_ = x + + def clear_end(self): + if self.has_end_: + self.has_end_ = 0 + self.end_ = 0 + + def has_end(self): return self.has_end_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_start()): self.set_start(x.start()) + if (x.has_end()): self.set_end(x.end()) + + def Equals(self, x): + if x is self: return 1 + if self.has_start_ != x.has_start_: return 0 + if self.has_start_ and self.start_ != x.start_: return 0 + if self.has_end_ != x.has_end_: return 0 + if self.has_end_ and self.end_ != x.end_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_start_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: start not set.') + if (not self.has_end_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: end not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.start_) + n += self.lengthVarInt64(self.end_) + return n + 2 + + def Clear(self): + self.clear_start() + self.clear_end() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.start_) + out.putVarInt32(16) + out.putVarInt64(self.end_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_start(d.getVarInt64()) + continue + if tt == 16: + self.set_end(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_start_: res+=prefix+("start: %s\n" % self.DebugFormatInt64(self.start_)) + if self.has_end_: res+=prefix+("end: %s\n" % self.DebugFormatInt64(self.end_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kstart = 1 + kend = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "start", + 2: "end", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompositeIndices(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.index_ = [] + if contents is not None: self.MergeFromString(contents) + + def index_size(self): return len(self.index_) + def index_list(self): return self.index_ + + def index(self, i): + return self.index_[i] + + def mutable_index(self, i): + return self.index_[i] + + def add_index(self): + x = CompositeIndex() + self.index_.append(x) + return x + + def clear_index(self): + self.index_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.index_size()): self.add_index().CopyFrom(x.index(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.index_) != len(x.index_): return 0 + for e1, e2 in zip(self.index_, x.index_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.index_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 1 * len(self.index_) + for i in xrange(len(self.index_)): n += self.lengthString(self.index_[i].ByteSize()) + return n + 0 + + def Clear(self): + self.clear_index() + + def OutputUnchecked(self, out): + for i in xrange(len(self.index_)): + out.putVarInt32(10) + out.putVarInt32(self.index_[i].ByteSize()) + self.index_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_index().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.index_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("index%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kindex = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "index", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class AddActionsRequest(ProtocolBuffer.ProtocolMessage): + has_transaction_ = 0 + + def __init__(self, contents=None): + self.transaction_ = Transaction() + self.action_ = [] + if contents is not None: self.MergeFromString(contents) + + def transaction(self): return self.transaction_ + + def mutable_transaction(self): self.has_transaction_ = 1; return self.transaction_ + + def clear_transaction(self):self.has_transaction_ = 0; self.transaction_.Clear() + + def has_transaction(self): return self.has_transaction_ + + def action_size(self): return len(self.action_) + def action_list(self): return self.action_ + + def action(self, i): + return self.action_[i] + + def mutable_action(self, i): + return self.action_[i] + + def add_action(self): + x = Action() + self.action_.append(x) + return x + + def clear_action(self): + self.action_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_transaction()): self.mutable_transaction().MergeFrom(x.transaction()) + for i in xrange(x.action_size()): self.add_action().CopyFrom(x.action(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_transaction_ != x.has_transaction_: return 0 + if self.has_transaction_ and self.transaction_ != x.transaction_: return 0 + if len(self.action_) != len(x.action_): return 0 + for e1, e2 in zip(self.action_, x.action_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_transaction_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: transaction not set.') + elif not self.transaction_.IsInitialized(debug_strs): initialized = 0 + for p in self.action_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.transaction_.ByteSize()) + n += 1 * len(self.action_) + for i in xrange(len(self.action_)): n += self.lengthString(self.action_[i].ByteSize()) + return n + 1 + + def Clear(self): + self.clear_transaction() + self.clear_action() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putVarInt32(self.transaction_.ByteSize()) + self.transaction_.OutputUnchecked(out) + for i in xrange(len(self.action_)): + out.putVarInt32(18) + out.putVarInt32(self.action_[i].ByteSize()) + self.action_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_transaction().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_action().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_transaction_: + res+=prefix+"transaction <\n" + res+=self.transaction_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt=0 + for e in self.action_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("action%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + ktransaction = 1 + kaction = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "transaction", + 2: "action", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class AddActionsResponse(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + pass + if contents is not None: self.MergeFromString(contents) + + + def MergeFrom(self, x): + assert x is not self + + def Equals(self, x): + if x is self: return 1 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + return n + 0 + + def Clear(self): + pass + + def OutputUnchecked(self, out): + pass + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + }, 0) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + }, 0, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class BeginTransactionRequest(ProtocolBuffer.ProtocolMessage): + has_app_ = 0 + app_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_app_): n += 1 + self.lengthString(len(self.app_)) + return n + 0 + + def Clear(self): + self.clear_app() + + def OutputUnchecked(self, out): + if (self.has_app_): + out.putVarInt32(10) + out.putPrefixedString(self.app_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CommitResponse(ProtocolBuffer.ProtocolMessage): + has_cost_ = 0 + cost_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def cost(self): + if self.cost_ is None: + self.lazy_init_lock_.acquire() + try: + if self.cost_ is None: self.cost_ = Cost() + finally: + self.lazy_init_lock_.release() + return self.cost_ + + def mutable_cost(self): self.has_cost_ = 1; return self.cost() + + def clear_cost(self): + if self.has_cost_: + self.has_cost_ = 0; + if self.cost_ is not None: self.cost_.Clear() + + def has_cost(self): return self.has_cost_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_cost()): self.mutable_cost().MergeFrom(x.cost()) + + def Equals(self, x): + if x is self: return 1 + if self.has_cost_ != x.has_cost_: return 0 + if self.has_cost_ and self.cost_ != x.cost_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_cost_ and not self.cost_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_cost_): n += 1 + self.lengthString(self.cost_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_cost() + + def OutputUnchecked(self, out): + if (self.has_cost_): + out.putVarInt32(10) + out.putVarInt32(self.cost_.ByteSize()) + self.cost_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_cost().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_cost_: + res+=prefix+"cost <\n" + res+=self.cost_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcost = 1 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "cost", + }, 1) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + }, 1, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['Transaction','Query','Query_Filter','Query_Order','CompiledQuery','CompiledQuery_PrimaryScan','CompiledQuery_MergeJoinScan','CompiledQuery_EntityFilter','CompiledCursor','CompiledCursor_Position','RunCompiledQueryRequest','Cursor','Error','Cost','GetRequest','GetResponse','GetResponse_Entity','PutRequest','PutResponse','TouchRequest','TouchResponse','DeleteRequest','DeleteResponse','NextRequest','QueryResult','GetSchemaRequest','Schema','GetNamespacesRequest','GetNamespacesResponse','AllocateIdsRequest','AllocateIdsResponse','CompositeIndices','AddActionsRequest','AddActionsResponse','BeginTransactionRequest','CommitResponse'] diff --git a/google_appengine/google/appengine/datastore/datastore_pb.pyc b/google_appengine/google/appengine/datastore/datastore_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..022e706f1fa8b580974ee3afb02aa394fa325872 GIT binary patch literal 267147 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 P{od~n6&H%P7VrB1A?C|9 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/datastore_sqlite_stub.py b/google_appengine/google/appengine/datastore/datastore_sqlite_stub.py new file mode 100644 index 0000000..ebabeaa --- /dev/null +++ b/google_appengine/google/appengine/datastore/datastore_sqlite_stub.py @@ -0,0 +1,1574 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""SQlite-based stub for the Python datastore API. + +Entities are stored in an sqlite database in a similar fashion to the production +datastore. + +Transactions are serialized through __tx_lock. Each transaction acquires it +when it begins and releases it when it commits or rolls back. +""" + + + + + + +import array +import itertools +import logging +import md5 +import sys +import threading + +from google.appengine.datastore import entity_pb +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_stub +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import datastore_errors +from google.appengine.datastore import datastore_index +from google.appengine.datastore import datastore_pb +from google.appengine.datastore import sortable_pb_encoder +from google.appengine.runtime import apiproxy_errors + +try: + import pysqlite2.dbapi2 as sqlite3 +except ImportError: + import sqlite3 + +try: + __import__('google.appengine.api.labs.taskqueue.taskqueue_service_pb') + taskqueue_service_pb = sys.modules.get( + 'google.appengine.api.labs.taskqueue.taskqueue_service_pb') +except ImportError: + from google.appengine.api.taskqueue import taskqueue_service_pb + + +import __builtin__ +buffer = __builtin__.buffer + + +entity_pb.Reference.__hash__ = lambda self: hash(self.Encode()) +datastore_pb.Query.__hash__ = lambda self: hash(self.Encode()) +datastore_pb.Transaction.__hash__ = lambda self: hash(self.Encode()) +datastore_pb.Cursor.__hash__ = lambda self: hash(self.Encode()) + + +_MAXIMUM_RESULTS = 1000 + + +_MAX_QUERY_OFFSET = 1000 + + +_MAX_QUERY_COMPONENTS = 63 + + +_BATCH_SIZE = 20 + + +_MAX_ACTIONS_PER_TXN = 5 + + +_MAX_TIMEOUT = 5.0 + + +_OPERATOR_MAP = { + datastore_pb.Query_Filter.LESS_THAN: '<', + datastore_pb.Query_Filter.LESS_THAN_OR_EQUAL: '<=', + datastore_pb.Query_Filter.EQUAL: '=', + datastore_pb.Query_Filter.GREATER_THAN: '>', + datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL: '>=', +} + + +_ORDER_MAP = { + datastore_pb.Query_Order.ASCENDING: 'ASC', + datastore_pb.Query_Order.DESCENDING: 'DESC', +} + +_CORE_SCHEMA = """ +CREATE TABLE IF NOT EXISTS Apps ( + app_id TEXT NOT NULL PRIMARY KEY, + indexes BLOB); + +CREATE TABLE IF NOT EXISTS Namespaces ( + app_id TEXT NOT NULL, + name_space TEXT NOT NULL, + PRIMARY KEY (app_id, name_space)); + +CREATE TABLE IF NOT EXISTS IdSeq ( + prefix TEXT NOT NULL PRIMARY KEY, + next_id INT NOT NULL); +""" + +_NAMESPACE_SCHEMA = """ +CREATE TABLE "%(prefix)s!Entities" ( + __path__ BLOB NOT NULL PRIMARY KEY, + kind TEXT NOT NULL, + entity BLOB NOT NULL); +CREATE INDEX "%(prefix)s!EntitiesByKind" ON "%(prefix)s!Entities" ( + kind ASC, + __path__ ASC); + +CREATE TABLE "%(prefix)s!EntitiesByProperty" ( + kind TEXT NOT NULL, + name TEXT NOT NULL, + value BLOB NOT NULL, + __path__ BLOB NOT NULL REFERENCES Entities, + PRIMARY KEY(kind ASC, name ASC, value ASC, __path__ ASC) ON CONFLICT IGNORE); +CREATE INDEX "%(prefix)s!EntitiesByPropertyDesc" + ON "%(prefix)s!EntitiesByProperty" ( + kind ASC, + name ASC, + value DESC, + __path__ ASC); +CREATE INDEX "%(prefix)s!EntitiesByPropertyKey" + ON "%(prefix)s!EntitiesByProperty" ( + __path__ ASC); + +INSERT OR IGNORE INTO Apps (app_id) VALUES ('%(app_id)s'); +INSERT INTO Namespaces (app_id, name_space) + VALUES ('%(app_id)s', '%(name_space)s'); +INSERT OR IGNORE INTO IdSeq VALUES ('%(prefix)s', 1); +""" + + +def ReferencePropertyToReference(refprop): + ref = entity_pb.Reference() + ref.set_app(refprop.app()) + if refprop.has_name_space(): + ref.set_name_space(refprop.name_space()) + for pathelem in refprop.pathelement_list(): + ref.mutable_path().add_element().CopyFrom(pathelem) + return ref + + +class QueryCursor(object): + """Encapsulates a database cursor and provides methods to fetch results.""" + + def __init__(self, query, db_cursor): + """Constructor. + + Args: + query: A Query PB. + db_cursor: An SQLite cursor returning n+2 columns. The first 2 columns + must be the path of the entity and the entity itself, while the + remaining columns must be the sort columns for the query. + """ + self.__query = query + self.app = query.app() + self.__cursor = db_cursor + self.__seen = set() + + self.__position = ('', '') + + self.__next_result = (None, None) + + if query.has_limit(): + self.limit = query.limit() + query.offset() + else: + self.limit = None + + def Count(self): + """Counts results, up to the query's limit. + + Note this method does not deduplicate results, so the query it was generated + from should have the 'distinct' clause applied. + + Returns: + int: Result count. + """ + count = 0 + while self.limit is None or count < self.limit: + row = self.__cursor.fetchone() + if not row: + break + count += 1 + return count + + def _EncodeCompiledCursor(self, cc): + """Encodes the current position in the query as a compiled cursor. + + Args: + cc: The compiled cursor to fill out. + """ + position = cc.add_position() + position.set_start_key(self.__position[0]) + + def _GetResult(self): + """Returns the next result from the result set, without deduplication. + + Returns: + (path, value): The path and value of the next result. + """ + if self.__position[1]: + self.__position = (self.__position[1], None) + + if not self.__cursor: + return None, None + row = self.__cursor.fetchone() + if not row: + self.__cursor = None + return None, None + path, data, position_parts = str(row[0]), row[1], row[2:] + position = ''.join(str(x) for x in position_parts) + if (self.__query.has_end_compiled_cursor() and position > + self.__query.end_compiled_cursor().position(0).start_key()): + self.__cursor = None + return None, None + + self.__position = (self.__position[0], position) + return path, data + + def _Next(self): + """Fetches the next unique result from the result set. + + Returns: + A datastore_pb.EntityProto instance. + """ + if self._HasNext(): + self.__seen.add(self.__next_result[0]) + entity = entity_pb.EntityProto(self.__next_result[1]) + self.__next_result = None, None + return entity + return None + + def _HasNext(self): + """Prefetches the next result and returns true if successful + + Returns: + A boolean that indicates if there are more results. + """ + while self.__cursor and ( + not self.__next_result[0] or self.__next_result[0] in self.__seen): + self.__next_result = self._GetResult() + if self.__next_result[0]: + return True + return False + + def Skip(self, count): + """Skips the specified number of unique results. + + Args: + count: Number of results to skip. + + Returns: + A number indicating how many results where actually skipped. + """ + for i in xrange(count): + if not self._Next(): + return i + return count + + def ResumeFromCompiledCursor(self, cc): + """Resumes a query from a compiled cursor. + + Args: + cc: The compiled cursor to resume from. + """ + + target_position = cc.position(0).start_key() + if (self.__query.has_end_compiled_cursor() and target_position >= + self.__query.end_compiled_cursor().position(0).start_key()): + self.__position = (target_position, target_position) + self.__cursor = None + return + + while self.__position[1] <= target_position and self.__cursor: + self.__next_result = self._GetResult() + + def PopulateQueryResult(self, count, offset, result): + """Populates a QueryResult PB with results from the cursor. + + Args: + count: The number of results to retrieve. + offset: The number of results to skip + result: out: A query_result PB. + """ + + limited_offset = min(offset, _MAX_QUERY_OFFSET) + if limited_offset: + result.set_skipped_results(self.Skip(limited_offset)) + + if offset == limited_offset: + if count > _MAXIMUM_RESULTS: + count = _MAXIMUM_RESULTS + + result_list = result.result_list() + while len(result_list) < count: + if self.limit is not None and len(self.__seen) >= self.limit: + break + entity = self._Next() + if entity is None: + break + result_list.append(entity) + + result.set_keys_only(self.__query.keys_only()) + result.set_more_results(self._HasNext()) + self._EncodeCompiledCursor(result.mutable_compiled_cursor()) + + +class DatastoreSqliteStub(apiproxy_stub.APIProxyStub): + """Persistent stub for the Python datastore API. + + Stores all entities in an SQLite database. A DatastoreSqliteStub instance + handles a single app's data. + """ + + WRITE_ONLY = entity_pb.CompositeIndex.WRITE_ONLY + READ_WRITE = entity_pb.CompositeIndex.READ_WRITE + DELETED = entity_pb.CompositeIndex.DELETED + ERROR = entity_pb.CompositeIndex.ERROR + + _INDEX_STATE_TRANSITIONS = { + WRITE_ONLY: frozenset((READ_WRITE, DELETED, ERROR)), + READ_WRITE: frozenset((DELETED,)), + ERROR: frozenset((DELETED,)), + DELETED: frozenset((ERROR,)), + } + + READ_ERROR_MSG = ('Data in %s is corrupt or a different version. ' + 'Try running with the --clear_datastore flag.\n%r') + + def __init__(self, + app_id, + datastore_file, + require_indexes=False, + verbose=False, + service_name='datastore_v3', + trusted=False): + """Constructor. + + Initializes the SQLite database if necessary. + + Args: + app_id: string + datastore_file: string, path to sqlite database. Use None to create an + in-memory database. + require_indexes: bool, default False. If True, composite indexes must + exist in index.yaml for queries that need them. + verbose: bool, default False. If True, logs all select statements. + service_name: Service name expected for all calls. + trusted: bool, default False. If True, this stub allows an app to access + the data of another app. + """ + apiproxy_stub.APIProxyStub.__init__(self, service_name) + + assert isinstance(app_id, basestring) and app_id + self.__app_id = app_id + self.__datastore_file = datastore_file + self.SetTrusted(trusted) + + self.__tx_actions = [] + + self.__require_indexes = require_indexes + self.__verbose = verbose + + self.__id_map = {} + self.__id_lock = threading.Lock() + + self.__connection = sqlite3.connect( + self.__datastore_file or ':memory:', + timeout=_MAX_TIMEOUT, + check_same_thread=False) + self.__connection_lock = threading.RLock() + self.__current_transaction = None + self.__next_tx_handle = 1 + + self.__tx_writes = {} + self.__tx_deletes = set() + + self.__next_cursor_id = 1 + self.__cursor_lock = threading.Lock() + self.__cursors = {} + + self.__namespaces = set() + + self.__indexes = {} + self.__index_lock = threading.Lock() + + self.__query_history = {} + + try: + self.__Init() + except sqlite3.DatabaseError, e: + raise datastore_errors.InternalError(self.READ_ERROR_MSG % + (self.__datastore_file, e)) + + def __Init(self): + self.__connection.executescript(_CORE_SCHEMA) + self.__connection.commit() + + c = self.__connection.execute('SELECT app_id, name_space FROM Namespaces') + self.__namespaces = set(c.fetchall()) + + c = self.__connection.execute('SELECT app_id, indexes FROM Apps') + for app_id, index_proto in c.fetchall(): + index_map = self.__indexes.setdefault(app_id, {}) + if not index_proto: + continue + indexes = datastore_pb.CompositeIndices(index_proto) + for index in indexes.index_list(): + index_map.setdefault(index.definition().entity_type(), []).append(index) + + def Clear(self): + """Clears the datastore.""" + conn = self.__GetConnection(None) + try: + c = conn.execute( + "SELECT tbl_name FROM sqlite_master WHERE type = 'table'") + for row in c.fetchall(): + conn.execute('DROP TABLE "%s"' % row) + conn.commit() + finally: + self.__ReleaseConnection(conn, None) + + self.__namespaces = set() + self.__indexes = {} + self.__cursors = {} + self.__query_history = {} + + self.__Init() + + def Read(self): + """Reads the datastore from disk. + + Noop for compatibility with file stub. + """ + pass + + def Write(self): + """Writes the datastore to disk. + + Noop for compatibility with file stub. + """ + pass + + def SetTrusted(self, trusted): + """Set/clear the trusted bit in the stub. + + This bit indicates that the app calling the stub is trusted. A + trusted app can write to datastores of other apps. + + Args: + trusted: boolean. + """ + self.__trusted = trusted + + @staticmethod + def __MakeParamList(size): + """Returns a comma separated list of sqlite substitution parameters. + + Args: + size: Number of parameters in returned list. + Returns: + A comma separated list of substitution parameters. + """ + return ','.join('?' * size) + + @staticmethod + def __GetEntityKind(key): + if isinstance(key, entity_pb.EntityProto): + key = key.key() + return key.path().element_list()[-1].type() + + @staticmethod + def __EncodeIndexPB(pb): + if isinstance(pb, entity_pb.PropertyValue) and pb.has_uservalue(): + userval = entity_pb.PropertyValue() + userval.mutable_uservalue().set_email(pb.uservalue().email()) + userval.mutable_uservalue().set_auth_domain(pb.uservalue().auth_domain()) + userval.mutable_uservalue().set_gaiaid(0) + pb = userval + encoder = sortable_pb_encoder.Encoder() + pb.Output(encoder) + return buffer(encoder.buffer().tostring()) + + @staticmethod + def __AddQueryParam(params, param): + params.append(param) + return len(params) + + @staticmethod + def __CreateFilterString(filter_list, params): + """Transforms a filter list into an SQL WHERE clause. + + Args: + filter_list: The list of (property, operator, value) filters + to transform. A value_type of -1 indicates no value type comparison + should be done. + params: out: A list of parameters to pass to the query. + Returns: + An SQL 'where' clause. + """ + clauses = [] + for prop, operator, value in filter_list: + sql_op = _OPERATOR_MAP[operator] + + value_index = DatastoreSqliteStub.__AddQueryParam(params, value) + clauses.append('%s %s :%d' % (prop, sql_op, value_index)) + + filters = ' AND '.join(clauses) + if filters: + filters = 'WHERE ' + filters + return filters + + @staticmethod + def __CreateOrderString(order_list): + """Returns an 'ORDER BY' clause from the given list of orders. + + Args: + order_list: A list of (field, order) tuples. + Returns: + An SQL ORDER BY clause. + """ + orders = ', '.join('%s %s' % (x[0], _ORDER_MAP[x[1]]) for x in order_list) + if orders: + orders = 'ORDER BY ' + orders + return orders + + def __ValidateAppId(self, app_id): + """Verify that this is the stub for app_id. + + Args: + app_id: An application ID. + + Raises: + datastore_errors.BadRequestError: if this is not the stub for app_id. + """ + assert app_id + if not self.__trusted and app_id != self.__app_id: + raise datastore_errors.BadRequestError( + 'app %s cannot access app %s\'s data' % (self.__app_id, app_id)) + + def __ValidateTransaction(self, tx): + """Verify that this transaction exists and is valid. + + Args: + tx: datastore_pb.Transaction + + Raises: + datastore_errors.BadRequestError: if the tx is valid or doesn't exist. + """ + assert isinstance(tx, datastore_pb.Transaction) + self.__ValidateAppId(tx.app()) + if tx.handle() != self.__current_transaction: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Transaction %s not found' % tx) + + def __ValidateKey(self, key): + """Validate this key. + + Args: + key: entity_pb.Reference + + Raises: + datastore_errors.BadRequestError: if the key is invalid + """ + assert isinstance(key, entity_pb.Reference) + + self.__ValidateAppId(key.app()) + + for elem in key.path().element_list(): + if elem.has_id() == elem.has_name(): + raise datastore_errors.BadRequestError( + 'each key path element should have id or name but not both: %r' + % key) + + def __GetConnection(self, transaction): + """Retrieves a connection to the SQLite DB. + + If a transaction is supplied, the transaction's connection is returned; + otherwise a fresh connection is returned. + + Args: + transaction: A Transaction PB. + Returns: + An SQLite connection object. + """ + self.__connection_lock.acquire() + request_tx = transaction and transaction.handle() + if request_tx == 0: + request_tx = None + if request_tx != self.__current_transaction: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Only one concurrent transaction per thread is permitted.') + return self.__connection + + def __ReleaseConnection(self, conn, transaction, rollback=False): + """Releases a connection for use by other operations. + + If a transaction is supplied, no action is taken. + + Args: + conn: An SQLite connection object. + transaction: A Transaction PB. + rollback: If True, roll back the database TX instead of committing it. + """ + if not transaction or not transaction.has_handle(): + if rollback: + conn.rollback() + else: + conn.commit() + self.__connection_lock.release() + + def __ConfigureNamespace(self, conn, prefix, app_id, name_space): + """Ensures the relevant tables and indexes exist. + + Args: + conn: An SQLite database connection. + prefix: The namespace prefix to configure. + app_id: The app ID. + name_space: The per-app namespace name. + """ + format_args = {'app_id': app_id, 'name_space': name_space, 'prefix': prefix} + conn.executescript(_NAMESPACE_SCHEMA % format_args) + conn.commit() + + def __WriteIndexData(self, conn, app): + """Writes index data to disk. + + Args: + conn: An SQLite connection. + app: The app ID to write indexes for. + """ + indices = datastore_pb.CompositeIndices() + for indexes in self.__indexes[app].values(): + indices.index_list().extend(indexes) + + conn.execute('UPDATE Apps SET indexes = ? WHERE app_id = ?', + (app, indices.Encode())) + + def __GetTablePrefix(self, data): + """Returns the namespace prefix for a query. + + Args: + data: An Entity, Key or Query PB, or an (app_id, ns) tuple. + Returns: + A valid table prefix + """ + if isinstance(data, entity_pb.EntityProto): + data = data.key() + if not isinstance(data, tuple): + data = (data.app(), data.name_space()) + prefix = ('%s!%s' % data).replace('"', '""') + if data not in self.__namespaces: + self.__namespaces.add(data) + self.__ConfigureNamespace(self.__connection, prefix, *data) + return prefix + + def __DeleteRows(self, conn, paths, table): + """Deletes rows from a table. + + Args: + conn: An SQLite connection. + paths: Paths to delete. + table: The table to delete from. + Returns: + The number of rows deleted. + """ + c = conn.execute('DELETE FROM "%s" WHERE __path__ IN (%s)' + % (table, self.__MakeParamList(len(paths))), + paths) + return c.rowcount + + def __DeleteEntityRows(self, conn, keys, table): + """Deletes rows from the specified table that index the keys provided. + + Args: + conn: A database connection. + keys: A list of keys to delete index entries for. + table: The table to delete from. + Returns: + The number of rows deleted. + """ + keys = sorted((x.app(), x.name_space(), x) for x in keys) + for (app_id, ns), group in itertools.groupby(keys, lambda x: x[:2]): + path_strings = [self.__EncodeIndexPB(x[2].path()) for x in group] + prefix = self.__GetTablePrefix((app_id, ns)) + return self.__DeleteRows(conn, path_strings, '%s!%s' % (prefix, table)) + + def __DeleteIndexEntries(self, conn, keys): + """Deletes entities from the index. + + Args: + conn: An SQLite connection. + keys: A list of keys to delete. + """ + self.__DeleteEntityRows(conn, keys, 'EntitiesByProperty') + + def __InsertEntities(self, conn, entities): + """Inserts or updates entities in the DB. + + Args: + conn: A database connection. + entities: A list of entities to store. + """ + + def RowGenerator(entities): + for unused_prefix, e in entities: + yield (self.__EncodeIndexPB(e.key().path()), + self.__GetEntityKind(e), + buffer(e.Encode())) + + entities = sorted((self.__GetTablePrefix(x), x) for x in entities) + for prefix, group in itertools.groupby(entities, lambda x: x[0]): + conn.executemany( + 'INSERT OR REPLACE INTO "%s!Entities" VALUES (?, ?, ?)' % prefix, + RowGenerator(group)) + + def __InsertIndexEntries(self, conn, entities): + """Inserts index entries for the supplied entities. + + Args: + conn: A database connection. + entities: A list of entities to create index entries for. + """ + + def RowGenerator(entities): + for unused_prefix, e in entities: + for p in e.property_list(): + yield (self.__GetEntityKind(e), + p.name(), + self.__EncodeIndexPB(p.value()), + self.__EncodeIndexPB(e.key().path())) + entities = sorted((self.__GetTablePrefix(x), x) for x in entities) + for prefix, group in itertools.groupby(entities, lambda x: x[0]): + conn.executemany( + 'INSERT INTO "%s!EntitiesByProperty" VALUES (?, ?, ?, ?)' % prefix, + RowGenerator(group)) + + def __AllocateIds(self, conn, prefix, size=None, max=None): + """Allocates IDs. + + Args: + conn: An Sqlite connection object. + prefix: A table namespace prefix. + size: Number of IDs to allocate. + max: Upper bound of IDs to allocate + + Returns: + int: The beginning of a range of size IDs + """ + self.__id_lock.acquire() + ret = None + if size is not None: + assert size > 0 + next_id, block_size = self.__id_map.get(prefix, (0, 0)) + if not block_size: + block_size = (size / 1000 + 1) * 1000 + c = conn.execute('SELECT next_id FROM IdSeq WHERE prefix = ? LIMIT 1', + (prefix,)) + next_id = c.fetchone()[0] + c = conn.execute( + 'UPDATE IdSeq SET next_id = next_id + ? WHERE prefix = ?', + (block_size, prefix)) + assert c.rowcount == 1 + + if size > block_size: + c = conn.execute('SELECT next_id FROM IdSeq WHERE prefix = ? LIMIT 1', + (prefix,)) + ret = c.fetchone()[0] + c = conn.execute( + 'UPDATE IdSeq SET next_id = next_id + ? WHERE prefix = ?', + (size, prefix)) + assert c.rowcount == 1 + else: + ret = next_id; + next_id += size + block_size -= size + self.__id_map[prefix] = (next_id, block_size) + else: + c = conn.execute('SELECT next_id FROM IdSeq WHERE prefix = ? LIMIT 1', + (prefix,)) + ret = c.fetchone()[0] + if max and max >= ret: + c = conn.execute( + 'UPDATE IdSeq SET next_id = ? WHERE prefix = ?', + (max + 1, prefix)) + assert c.rowcount == 1 + self.__id_lock.release() + return ret + + def MakeSyncCall(self, service, call, request, response): + """The main RPC entry point. service must be 'datastore_v3'.""" + self.AssertPbIsInitialized(request) + try: + apiproxy_stub.APIProxyStub.MakeSyncCall(self, service, call, request, + response) + except sqlite3.OperationalError, e: + if e.args[0] == 'database is locked': + raise datastore_errors.Timeout('Database is locked.') + else: + raise + self.AssertPbIsInitialized(response) + + def AssertPbIsInitialized(self, pb): + """Raises an exception if the given PB is not initialized and valid.""" + explanation = [] + assert pb.IsInitialized(explanation), explanation + pb.Encode() + + def QueryHistory(self): + """Returns a dict that maps Query PBs to times they've been run.""" + return dict((pb, times) for pb, times in self.__query_history.items() if + pb.app() == self.__app_id) + + def __PutEntities(self, conn, entities): + self.__DeleteIndexEntries(conn, [e.key() for e in entities]) + self.__InsertEntities(conn, entities) + self.__InsertIndexEntries(conn, entities) + + def __DeleteEntities(self, conn, keys): + self.__DeleteIndexEntries(conn, keys) + self.__DeleteEntityRows(conn, keys, 'Entities') + + def _Dynamic_Put(self, put_request, put_response): + conn = self.__GetConnection(put_request.transaction()) + try: + entities = put_request.entity_list() + for entity in entities: + self.__ValidateKey(entity.key()) + + for prop in itertools.chain(entity.property_list(), + entity.raw_property_list()): + if prop.value().has_uservalue(): + uid = md5.new(prop.value().uservalue().email().lower()).digest() + uid = '1' + ''.join(['%02d' % ord(x) for x in uid])[:20] + prop.mutable_value().mutable_uservalue().set_obfuscated_gaiaid(uid) + + assert entity.has_key() + assert entity.key().path().element_size() > 0 + + last_path = entity.key().path().element_list()[-1] + if last_path.id() == 0 and not last_path.has_name(): + id_ = self.__AllocateIds(conn, self.__GetTablePrefix(entity.key()), 1) + last_path.set_id(id_) + + assert entity.entity_group().element_size() == 0 + group = entity.mutable_entity_group() + root = entity.key().path().element(0) + group.add_element().CopyFrom(root) + + else: + assert (entity.has_entity_group() and + entity.entity_group().element_size() > 0) + + if put_request.transaction().handle(): + self.__tx_writes[entity.key()] = entity + self.__tx_deletes.discard(entity.key()) + + if not put_request.transaction().handle(): + self.__PutEntities(conn, entities) + put_response.key_list().extend([e.key() for e in entities]) + finally: + self.__ReleaseConnection(conn, put_request.transaction()) + + def _Dynamic_Get(self, get_request, get_response): + conn = self.__GetConnection(get_request.transaction()) + try: + for key in get_request.key_list(): + self.__ValidateAppId(key.app()) + prefix = self.__GetTablePrefix(key) + c = conn.execute( + 'SELECT entity FROM "%s!Entities" WHERE __path__ = ?' % (prefix,), + (self.__EncodeIndexPB(key.path()),)) + group = get_response.add_entity() + row = c.fetchone() + if row: + group.mutable_entity().ParseFromString(row[0]) + finally: + self.__ReleaseConnection(conn, get_request.transaction()) + + def _Dynamic_Delete(self, delete_request, delete_response): + conn = self.__GetConnection(delete_request.transaction()) + try: + for key in delete_request.key_list(): + self.__ValidateAppId(key.app()) + if delete_request.transaction().handle(): + self.__tx_deletes.add(key) + self.__tx_writes.pop(key, None) + + if not delete_request.transaction().handle(): + self.__DeleteEntities(conn, delete_request.key_list()) + finally: + self.__ReleaseConnection(conn, delete_request.transaction()) + + def __GenerateFilterInfo(self, filters, query): + """Transform a list of filters into a more usable form. + + Args: + filters: A list of filter PBs. + query: The query to generate filter info for. + Returns: + A dict mapping property names to lists of (op, value) tuples. + """ + filter_info = {} + for filt in filters: + assert filt.property_size() == 1 + prop = filt.property(0) + value = prop.value() + if prop.name() == '__key__': + value = ReferencePropertyToReference(value.referencevalue()) + assert value.app() == query.app() + assert value.name_space() == query.name_space() + value = value.path() + filter_info.setdefault(prop.name(), []).append( + (filt.op(), self.__EncodeIndexPB(value))) + return filter_info + + def __GenerateOrderInfo(self, orders): + """Transform a list of orders into a more usable form. + + Args: + orders: A list of order PBs. + Returns: + A list of (property, direction) tuples. + """ + orders = [(order.property(), order.direction()) for order in orders] + if orders and orders[-1] == ('__key__', datastore_pb.Query_Order.ASCENDING): + orders.pop() + return orders + + def __GetPrefixRange(self, prefix): + """Returns a (min, max) range that encompasses the given prefix. + + Args: + prefix: A string prefix to filter for. Must be a PB encodable using + __EncodeIndexPB. + Returns: + (min, max): Start and end string values to filter on. + """ + ancestor_min = self.__EncodeIndexPB(prefix) + ancestor_max = buffer(str(ancestor_min) + '\xfb\xff\xff\xff\x89') + return ancestor_min, ancestor_max + + def __KindQuery(self, query, filter_info, order_info): + """Performs kind only, kind and ancestor, and ancestor only queries.""" + if not (set(filter_info.keys()) | + set(x[0] for x in order_info)).issubset(['__key__']): + return None + if len(order_info) > 1: + return None + + filters = [] + filters.extend(('__path__', op, value) for op, value + in filter_info.get('__key__', [])) + if query.has_kind(): + filters.append(('kind', datastore_pb.Query_Filter.EQUAL, query.kind())) + if query.has_ancestor(): + amin, amax = self.__GetPrefixRange(query.ancestor().path()) + filters.append(('__path__', + datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL, amin)) + filters.append(('__path__', datastore_pb.Query_Filter.LESS_THAN, amax)) + + if order_info: + orders = [('__path__', order_info[0][1])] + else: + orders = [('__path__', datastore_pb.Query_Order.ASCENDING)] + + params = [] + query = ('SELECT Entities.__path__, Entities.entity, %s ' + 'FROM "%s!Entities" AS Entities %s %s' % ( + ','.join(x[0] for x in orders), + self.__GetTablePrefix(query), + self.__CreateFilterString(filters, params), + self.__CreateOrderString(orders))) + return query, params + + def __SinglePropertyQuery(self, query, filter_info, order_info): + """Performs queries satisfiable by the EntitiesByProperty table.""" + property_names = set(filter_info.keys()) + property_names.update(x[0] for x in order_info) + property_names.discard('__key__') + if len(property_names) != 1: + return None + + property_name = property_names.pop() + filter_ops = filter_info.get(property_name, []) + + if len([1 for o, _ in filter_ops + if o == datastore_pb.Query_Filter.EQUAL]) > 1: + return None + + if len(order_info) > 1 or (order_info and order_info[0][0] == '__key__'): + return None + + if query.has_ancestor(): + return None + + if not query.has_kind(): + return None + + prefix = self.__GetTablePrefix(query) + filters = [] + filters.append(('EntitiesByProperty.kind', + datastore_pb.Query_Filter.EQUAL, query.kind())) + filters.append(('name', datastore_pb.Query_Filter.EQUAL, property_name)) + for op, value in filter_ops: + if property_name == '__key__': + filters.append(('EntitiesByProperty.__path__', op, value)) + else: + filters.append(('value', op, value)) + + orders = [('EntitiesByProperty.kind', datastore_pb.Query_Order.ASCENDING), + ('name', datastore_pb.Query_Order.ASCENDING)] + if order_info: + orders.append(('value', order_info[0][1])) + else: + orders.append(('value', datastore_pb.Query_Order.ASCENDING)) + orders.append(('EntitiesByProperty.__path__', + datastore_pb.Query_Order.ASCENDING)) + + params = [] + format_args = ( + ','.join(x[0] for x in orders[2:]), + prefix, + prefix, + self.__CreateFilterString(filters, params), + self.__CreateOrderString(orders)) + query = ('SELECT Entities.__path__, Entities.entity, %s ' + 'FROM "%s!EntitiesByProperty" AS EntitiesByProperty INNER JOIN ' + '"%s!Entities" AS Entities USING (__path__) %s %s' % format_args) + return query, params + + def __StarSchemaQueryPlan(self, query, filter_info, order_info): + """Executes a query using a 'star schema' based on EntitiesByProperty. + + A 'star schema' is a join between an objects table (Entities) and multiple + instances of a facts table (EntitiesByProperty). Ideally, this will result + in a merge join if the only filters are inequalities and the sort orders + match those in the index for the facts table; otherwise, the DB will do its + best to satisfy the query efficiently. + + Args: + query: The datastore_pb.Query PB. + filter_info: A dict mapping properties filtered on to (op, value) tuples. + order_info: A list of (property, direction) tuples. + Returns: + (query, params): An SQL query string and list of parameters for it. + """ + filter_sets = [] + for name, filter_ops in filter_info.items(): + filter_sets.extend((name, [x]) for x in filter_ops + if x[0] == datastore_pb.Query_Filter.EQUAL) + ineq_ops = [x for x in filter_ops + if x[0] != datastore_pb.Query_Filter.EQUAL] + if ineq_ops: + filter_sets.append((name, ineq_ops)) + + for prop, _ in order_info: + if prop == '__key__': + continue + if prop not in filter_info: + filter_sets.append((prop, [])) + + prefix = self.__GetTablePrefix(query) + + joins = [] + filters = [] + join_name_map = {} + for name, filter_ops in filter_sets: + join_name = 'ebp_%d' % (len(joins),) + join_name_map.setdefault(name, join_name) + joins.append( + 'INNER JOIN "%s!EntitiesByProperty" AS %s ' + 'ON Entities.__path__ = %s.__path__' + % (prefix, join_name, join_name)) + filters.append(('%s.kind' % join_name, datastore_pb.Query_Filter.EQUAL, + query.kind())) + filters.append(('%s.name' % join_name, datastore_pb.Query_Filter.EQUAL, + name)) + for op, value in filter_ops: + filters.append(('%s.value' % join_name, op, buffer(value))) + if query.has_ancestor(): + amin, amax = self.__GetPrefixRange(query.ancestor().path()) + filters.append(('%s.__path__' % join_name, + datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL, amin)) + filters.append(('%s.__path__' % join_name, + datastore_pb.Query_Filter.LESS_THAN, amax)) + + orders = [] + for prop, order in order_info: + if prop == '__key__': + orders.append(('Entities.__path__', order)) + else: + prop = '%s.value' % (join_name_map[prop],) + orders.append((prop, order)) + if not order_info or order_info[-1][0] != '__key__': + orders.append(('Entities.__path__', datastore_pb.Query_Order.ASCENDING)) + + params = [] + format_args = ( + ','.join(x[0] for x in orders), + prefix, + ' '.join(joins), + self.__CreateFilterString(filters, params), + self.__CreateOrderString(orders)) + query = ('SELECT Entities.__path__, Entities.entity, %s ' + 'FROM "%s!Entities" AS Entities %s %s %s' % format_args) + return query, params + + def __MergeJoinQuery(self, query, filter_info, order_info): + if order_info: + return None + if query.has_ancestor(): + return None + if not query.has_kind(): + return None + for filter_ops in filter_info.values(): + for op, _ in filter_ops: + if op != datastore_pb.Query_Filter.EQUAL: + return None + + return self.__StarSchemaQueryPlan(query, filter_info, order_info) + + def __LastResortQuery(self, query, filter_info, order_info): + """Last resort query plan that executes queries requring composite indexes. + + Args: + query: The datastore_pb.Query PB. + filter_info: A dict mapping properties filtered on to (op, value) tuples. + order_info: A list of (property, direction) tuples. + Returns: + (query, params): An SQL query string and list of parameters for it. + """ + if self.__require_indexes: + index = self.__FindIndexForQuery(query) + if not index: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.NEED_INDEX, + 'This query requires a composite index that is not defined. ' + 'You must update the index.yaml file in your application root.') + return self.__StarSchemaQueryPlan(query, filter_info, order_info) + + def __FindIndexForQuery(self, query): + """Finds an index that can be used to satisfy the provided query. + + Args: + query: A datastore_pb.Query PB. + Returns: + An entity_pb.CompositeIndex PB, if a suitable index exists; otherwise None + """ + unused_required, kind, ancestor, props, num_eq_filters = ( + datastore_index.CompositeIndexForQuery(query)) + required_key = (kind, ancestor, props) + indexes = self.__indexes.get(query.app(), {}).get(kind, []) + + eq_filters_set = set(props[:num_eq_filters]) + remaining_filters = props[num_eq_filters:] + for index in indexes: + definition = datastore_index.ProtoToIndexDefinition(index) + index_key = datastore_index.IndexToKey(definition) + if required_key == index_key: + return index + if num_eq_filters > 1 and (kind, ancestor) == index_key[:2]: + this_props = index_key[2] + this_eq_filters_set = set(this_props[:num_eq_filters]) + this_remaining_filters = this_props[num_eq_filters:] + if (eq_filters_set == this_eq_filters_set and + remaining_filters == this_remaining_filters): + return index + + _QUERY_STRATEGIES = [ + __KindQuery, + __SinglePropertyQuery, + __MergeJoinQuery, + __LastResortQuery, + ] + + def __GetQueryCursor(self, conn, query): + """Returns an SQLite query cursor for the provided query. + + Args: + conn: The SQLite connection. + query: A datastore_pb.Query protocol buffer. + Returns: + A QueryCursor object. + """ + if query.has_transaction() and not query.has_ancestor(): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Only ancestor queries are allowed inside transactions.') + + num_components = len(query.filter_list()) + len(query.order_list()) + if query.has_ancestor(): + num_components += 1 + if num_components > _MAX_QUERY_COMPONENTS: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + ('query is too large. may not have more than %s filters' + ' + sort orders ancestor total' % _MAX_QUERY_COMPONENTS)) + + app_id = query.app() + self.__ValidateAppId(app_id) + + filters, orders = datastore_index.Normalize(query.filter_list(), + query.order_list()) + + filter_info = self.__GenerateFilterInfo(filters, query) + order_info = self.__GenerateOrderInfo(orders) + + for strategy in DatastoreSqliteStub._QUERY_STRATEGIES: + result = strategy(self, query, filter_info, order_info) + if result: + break + else: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'No strategy found to satisfy query.') + + sql_stmt, params = result + + if self.__verbose: + logging.info("Executing statement '%s' with arguments %r", + sql_stmt, [str(x) for x in params]) + db_cursor = conn.execute(sql_stmt, params) + cursor = QueryCursor(query, db_cursor) + if query.has_compiled_cursor() and query.compiled_cursor().position_size(): + cursor.ResumeFromCompiledCursor(query.compiled_cursor()) + + clone = datastore_pb.Query() + clone.CopyFrom(query) + clone.clear_hint() + clone.clear_limit() + clone.clear_count() + clone.clear_offset() + self.__query_history[clone] = self.__query_history.get(clone, 0) + 1 + + return cursor + + def _Dynamic_RunQuery(self, query, query_result): + conn = self.__GetConnection(query.transaction()) + try: + cursor = self.__GetQueryCursor(conn, query) + + self.__cursor_lock.acquire() + cursor_id = self.__next_cursor_id + self.__next_cursor_id += 1 + self.__cursor_lock.release() + + cursor_pb = query_result.mutable_cursor() + cursor_pb.set_app(query.app()) + cursor_pb.set_cursor(cursor_id) + + if query.has_count(): + count = query.count() + elif query.has_limit(): + count = query.limit() + else: + count = _BATCH_SIZE + + cursor.PopulateQueryResult(count, query.offset(), query_result) + self.__cursors[cursor_pb] = cursor + finally: + self.__ReleaseConnection(conn, query.transaction()) + + def _Dynamic_Next(self, next_request, query_result): + self.__ValidateAppId(next_request.cursor().app()) + + try: + cursor = self.__cursors[next_request.cursor()] + except KeyError: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Cursor %d not found' % next_request.cursor().cursor()) + + assert cursor.app == next_request.cursor().app() + + count = _BATCH_SIZE + if next_request.has_count(): + count = next_request.count() + cursor.PopulateQueryResult(count, next_request.offset(), query_result) + + def _Dynamic_Count(self, query, integer64proto): + if query.has_limit(): + query.set_limit(min(query.limit(), _MAXIMUM_RESULTS)) + else: + query.set_limit(_MAXIMUM_RESULTS) + + conn = self.__GetConnection(query.transaction()) + try: + cursor = self.__GetQueryCursor(conn, query) + integer64proto.set_value(cursor.Count()) + finally: + self.__ReleaseConnection(conn, query.transaction()) + + def _Dynamic_BeginTransaction(self, request, transaction): + self.__ValidateAppId(request.app()) + + self.__connection_lock.acquire() + assert self.__current_transaction is None + handle = self.__next_tx_handle + self.__next_tx_handle += 1 + + transaction.set_app(request.app()) + transaction.set_handle(handle) + self.__current_transaction = handle + + def _Dynamic_AddActions(self, request, _): + + if ((len(self.__tx_actions) + request.add_request_size()) > + _MAX_ACTIONS_PER_TXN): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Too many messages, maximum allowed %s' % _MAX_ACTIONS_PER_TXN) + + new_actions = [] + for add_request in request.add_request_list(): + self.__ValidateTransaction(add_request.transaction()) + clone = taskqueue_service_pb.TaskQueueAddRequest() + clone.CopyFrom(add_request) + clone.clear_transaction() + new_actions.append(clone) + + self.__tx_actions.extend(new_actions) + + def _Dynamic_Commit(self, transaction, _): + assert self.__current_transaction == transaction.handle() + conn = self.__connection + + try: + self.__PutEntities(conn, self.__tx_writes.values()) + self.__DeleteEntities(conn, self.__tx_deletes) + + for action in self.__tx_actions: + try: + apiproxy_stub_map.MakeSyncCall( + 'taskqueue', 'Add', action, api_base_pb.VoidProto()) + except apiproxy_errors.ApplicationError, e: + logging.warning('Transactional task %s has been dropped, %s', + action, e) + finally: + self.__current_transaction = None + self.__tx_actions = [] + self.__tx_writes = {} + self.__tx_deletes = set() + self.__ReleaseConnection(conn, None) + + def _Dynamic_Rollback(self, transaction, _): + conn = self.__GetConnection(transaction) + self.__current_transaction = None + self.__tx_actions = [] + self.__tx_writes = {} + self.__tx_deletes = set() + self.__ReleaseConnection(conn, None, True) + + def _Dynamic_GetSchema(self, req, schema): + conn = self.__GetConnection(None) + try: + prefix = self.__GetTablePrefix(req) + + filters = [] + if req.has_start_kind(): + filters.append(('kind', datastore_pb.Query_Filter.GREATER_THAN_OR_EQUAL, + req.start_kind())) + if req.has_end_kind(): + filters.append(('kind', datastore_pb.Query_Filter.LESS_THAN_OR_EQUAL, + req.end_kind())) + + params = [] + if req.properties(): + sql_stmt = ('SELECT kind, name, value FROM "%s!EntitiesByProperty" %s ' + 'GROUP BY kind, name, substr(value, 1, 1) ORDER BY kind' + % (prefix, self.__CreateFilterString(filters, params))) + else: + sql_stmt = ('SELECT kind FROM "%s!Entities" %s GROUP BY kind' + % (prefix, self.__CreateFilterString(filters, params))) + c = conn.execute(sql_stmt, params) + + kind = None + current_name = None + kind_pb = None + for row in c.fetchall(): + if row[0] != kind: + if kind_pb: + schema.kind_list().append(kind_pb) + kind = row[0].encode('utf-8') + kind_pb = entity_pb.EntityProto() + kind_pb.mutable_key().set_app('') + kind_pb.mutable_key().mutable_path().add_element().set_type(kind) + kind_pb.mutable_entity_group() + + if req.properties(): + name, value_data = row[1:] + if current_name != name: + current_name = name + prop_pb = kind_pb.add_property() + prop_pb.set_name(name.encode('utf-8')) + prop_pb.set_multiple(False) + + value_decoder = sortable_pb_encoder.Decoder( + array.array('B', str(value_data))) + value_pb = prop_pb.mutable_value() + value_pb.Merge(value_decoder) + + if value_pb.has_int64value(): + value_pb.set_int64value(0) + if value_pb.has_booleanvalue(): + value_pb.set_booleanvalue(False) + if value_pb.has_stringvalue(): + value_pb.set_stringvalue('none') + if value_pb.has_doublevalue(): + value_pb.set_doublevalue(0.0) + if value_pb.has_pointvalue(): + value_pb.mutable_pointvalue().set_x(0.0) + value_pb.mutable_pointvalue().set_y(0.0) + if value_pb.has_uservalue(): + value_pb.mutable_uservalue().set_gaiaid(0) + value_pb.mutable_uservalue().set_email('none') + value_pb.mutable_uservalue().set_auth_domain('none') + value_pb.mutable_uservalue().clear_nickname() + value_pb.mutable_uservalue().clear_obfuscated_gaiaid() + if value_pb.has_referencevalue(): + value_pb.clear_referencevalue() + value_pb.mutable_referencevalue().set_app('none') + pathelem = value_pb.mutable_referencevalue().add_pathelement() + pathelem.set_type('none') + pathelem.set_name('none') + + if kind_pb: + schema.kind_list().append(kind_pb) + finally: + self.__ReleaseConnection(conn, None) + + def _Dynamic_AllocateIds(self, allocate_ids_request, allocate_ids_response): + conn = self.__GetConnection(None) + model_key = allocate_ids_request.model_key() + self.__ValidateAppId(model_key.app()) + if allocate_ids_request.has_size() and allocate_ids_request.has_max(): + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Both size and max cannot be set.') + + if allocate_ids_request.has_size(): + if allocate_ids_request.size() < 1: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Size must be greater than 0.') + first_id = self.__AllocateIds(conn, self.__GetTablePrefix(model_key), + size=allocate_ids_request.size()) + allocate_ids_response.set_start(first_id) + allocate_ids_response.set_end(first_id + allocate_ids_request.size() - 1) + else: + if allocate_ids_request.max() < 0: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Max must be greater than or equal to 0.') + first_id = self.__AllocateIds(conn, self.__GetTablePrefix(model_key), + max=allocate_ids_request.max()) + allocate_ids_response.set_start(first_id) + allocate_ids_response.set_end(max(allocate_ids_request.max(), + first_id - 1)) + + self.__ReleaseConnection(conn, None) + + def __FindIndex(self, index): + """Finds an existing index by definition. + + Args: + index: entity_pb.CompositeIndex + + Returns: + entity_pb.CompositeIndex, if it exists; otherwise None + """ + app_indexes = self.__indexes.get(index.app_id(), {}) + for stored_index in app_indexes.get(index.definition().entity_type(), []): + if index.definition() == stored_index.definition(): + return stored_index + + return None + + def _Dynamic_CreateIndex(self, index, id_response): + app_id = index.app_id() + kind = index.definition().entity_type() + + self.__ValidateAppId(app_id) + if index.id() != 0: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'New index id must be 0.') + + self.__index_lock.acquire() + try: + if self.__FindIndex(index): + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Index already exists.') + + next_id = max([idx.id() for x in self.__indexes.get(app_id, {}).values() + for idx in x] + [0]) + 1 + index.set_id(next_id) + id_response.set_value(next_id) + + clone = entity_pb.CompositeIndex() + clone.CopyFrom(index) + self.__indexes.setdefault(app_id, {}).setdefault(kind, []).append(clone) + + conn = self.__GetConnection(None) + try: + self.__WriteIndexData(conn, app_id) + finally: + self.__ReleaseConnection(conn, None) + finally: + self.__index_lock.release() + + def _Dynamic_GetIndices(self, app_str, composite_indices): + self.__ValidateAppId(app_str.value()) + + index_list = composite_indices.index_list() + for indexes in self.__indexes.get(app_str.value(), {}).values(): + index_list.extend(indexes) + + def _Dynamic_UpdateIndex(self, index, _): + self.__ValidateAppId(index.app_id()) + my_index = self.__FindIndex(index) + if not my_index: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + "Index doesn't exist.") + elif (index.state() != my_index.state() and + index.state() not in self._INDEX_STATE_TRANSITIONS[my_index.state()]): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Cannot move index state from %s to %s' % + (entity_pb.CompositeIndex.State_Name(my_index.state()), + (entity_pb.CompositeIndex.State_Name(index.state())))) + + self.__index_lock.acquire() + try: + my_index.set_state(index.state()) + finally: + self.__index_lock.release() + + def _Dynamic_DeleteIndex(self, index, _): + app_id = index.app_id() + kind = index.definition().entity_type() + self.__ValidateAppId(app_id) + + my_index = self.__FindIndex(index) + if not my_index: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + "Index doesn't exist.") + + conn = self.__GetConnection(None) + try: + self.__WriteIndexData(conn, app_id) + finally: + self.__ReleaseConnection(conn, None) + self.__index_lock.acquire() + try: + self.__indexes[app_id][kind].remove(my_index) + finally: + self.__index_lock.release() diff --git a/google_appengine/google/appengine/datastore/datastore_sqlite_stub.pyc b/google_appengine/google/appengine/datastore/datastore_sqlite_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..777a5372c247c976df078f76880ac6a6d7fc6fde GIT binary patch literal 59940 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 Z+uZv9;rUR4Z{}nd9&b 0: + tt = d.getVarInt32() + if tt == 8: + self.set_int64value(d.getVarInt64()) + continue + if tt == 16: + self.set_booleanvalue(d.getBoolean()) + continue + if tt == 26: + self.set_stringvalue(d.getPrefixedString()) + continue + if tt == 33: + self.set_doublevalue(d.getDouble()) + continue + if tt == 43: + self.mutable_pointvalue().TryMerge(d) + continue + if tt == 67: + self.mutable_uservalue().TryMerge(d) + continue + if tt == 99: + self.mutable_referencevalue().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_int64value_: res+=prefix+("int64Value: %s\n" % self.DebugFormatInt64(self.int64value_)) + if self.has_booleanvalue_: res+=prefix+("booleanValue: %s\n" % self.DebugFormatBool(self.booleanvalue_)) + if self.has_stringvalue_: res+=prefix+("stringValue: %s\n" % self.DebugFormatString(self.stringvalue_)) + if self.has_doublevalue_: res+=prefix+("doubleValue: %s\n" % self.DebugFormat(self.doublevalue_)) + if self.has_pointvalue_: + res+=prefix+"PointValue {\n" + res+=self.pointvalue_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + if self.has_uservalue_: + res+=prefix+"UserValue {\n" + res+=self.uservalue_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + if self.has_referencevalue_: + res+=prefix+"ReferenceValue {\n" + res+=self.referencevalue_.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kint64Value = 1 + kbooleanValue = 2 + kstringValue = 3 + kdoubleValue = 4 + kPointValueGroup = 5 + kPointValuex = 6 + kPointValuey = 7 + kUserValueGroup = 8 + kUserValueemail = 9 + kUserValueauth_domain = 10 + kUserValuenickname = 11 + kUserValuegaiaid = 18 + kUserValueobfuscated_gaiaid = 19 + kUserValuefederated_identity = 21 + kUserValuefederated_provider = 22 + kReferenceValueGroup = 12 + kReferenceValueapp = 13 + kReferenceValuename_space = 20 + kReferenceValuePathElementGroup = 14 + kReferenceValuePathElementtype = 15 + kReferenceValuePathElementid = 16 + kReferenceValuePathElementname = 17 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "int64Value", + 2: "booleanValue", + 3: "stringValue", + 4: "doubleValue", + 5: "PointValue", + 6: "x", + 7: "y", + 8: "UserValue", + 9: "email", + 10: "auth_domain", + 11: "nickname", + 12: "ReferenceValue", + 13: "app", + 14: "PathElement", + 15: "type", + 16: "id", + 17: "name", + 18: "gaiaid", + 19: "obfuscated_gaiaid", + 20: "name_space", + 21: "federated_identity", + 22: "federated_provider", + }, 22) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.DOUBLE, + 5: ProtocolBuffer.Encoder.STARTGROUP, + 6: ProtocolBuffer.Encoder.DOUBLE, + 7: ProtocolBuffer.Encoder.DOUBLE, + 8: ProtocolBuffer.Encoder.STARTGROUP, + 9: ProtocolBuffer.Encoder.STRING, + 10: ProtocolBuffer.Encoder.STRING, + 11: ProtocolBuffer.Encoder.STRING, + 12: ProtocolBuffer.Encoder.STARTGROUP, + 13: ProtocolBuffer.Encoder.STRING, + 14: ProtocolBuffer.Encoder.STARTGROUP, + 15: ProtocolBuffer.Encoder.STRING, + 16: ProtocolBuffer.Encoder.NUMERIC, + 17: ProtocolBuffer.Encoder.STRING, + 18: ProtocolBuffer.Encoder.NUMERIC, + 19: ProtocolBuffer.Encoder.STRING, + 20: ProtocolBuffer.Encoder.STRING, + 21: ProtocolBuffer.Encoder.STRING, + 22: ProtocolBuffer.Encoder.STRING, + }, 22, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Property(ProtocolBuffer.ProtocolMessage): + + BLOB = 14 + TEXT = 15 + BYTESTRING = 16 + ATOM_CATEGORY = 1 + ATOM_LINK = 2 + ATOM_TITLE = 3 + ATOM_CONTENT = 4 + ATOM_SUMMARY = 5 + ATOM_AUTHOR = 6 + GD_WHEN = 7 + GD_EMAIL = 8 + GEORSS_POINT = 9 + GD_IM = 10 + GD_PHONENUMBER = 11 + GD_POSTALADDRESS = 12 + GD_RATING = 13 + BLOBKEY = 17 + + _Meaning_NAMES = { + 14: "BLOB", + 15: "TEXT", + 16: "BYTESTRING", + 1: "ATOM_CATEGORY", + 2: "ATOM_LINK", + 3: "ATOM_TITLE", + 4: "ATOM_CONTENT", + 5: "ATOM_SUMMARY", + 6: "ATOM_AUTHOR", + 7: "GD_WHEN", + 8: "GD_EMAIL", + 9: "GEORSS_POINT", + 10: "GD_IM", + 11: "GD_PHONENUMBER", + 12: "GD_POSTALADDRESS", + 13: "GD_RATING", + 17: "BLOBKEY", + } + + def Meaning_Name(cls, x): return cls._Meaning_NAMES.get(x, "") + Meaning_Name = classmethod(Meaning_Name) + + has_meaning_ = 0 + meaning_ = 0 + has_meaning_uri_ = 0 + meaning_uri_ = "" + has_name_ = 0 + name_ = "" + has_value_ = 0 + has_multiple_ = 0 + multiple_ = 0 + + def __init__(self, contents=None): + self.value_ = PropertyValue() + if contents is not None: self.MergeFromString(contents) + + def meaning(self): return self.meaning_ + + def set_meaning(self, x): + self.has_meaning_ = 1 + self.meaning_ = x + + def clear_meaning(self): + if self.has_meaning_: + self.has_meaning_ = 0 + self.meaning_ = 0 + + def has_meaning(self): return self.has_meaning_ + + def meaning_uri(self): return self.meaning_uri_ + + def set_meaning_uri(self, x): + self.has_meaning_uri_ = 1 + self.meaning_uri_ = x + + def clear_meaning_uri(self): + if self.has_meaning_uri_: + self.has_meaning_uri_ = 0 + self.meaning_uri_ = "" + + def has_meaning_uri(self): return self.has_meaning_uri_ + + def name(self): return self.name_ + + def set_name(self, x): + self.has_name_ = 1 + self.name_ = x + + def clear_name(self): + if self.has_name_: + self.has_name_ = 0 + self.name_ = "" + + def has_name(self): return self.has_name_ + + def value(self): return self.value_ + + def mutable_value(self): self.has_value_ = 1; return self.value_ + + def clear_value(self):self.has_value_ = 0; self.value_.Clear() + + def has_value(self): return self.has_value_ + + def multiple(self): return self.multiple_ + + def set_multiple(self, x): + self.has_multiple_ = 1 + self.multiple_ = x + + def clear_multiple(self): + if self.has_multiple_: + self.has_multiple_ = 0 + self.multiple_ = 0 + + def has_multiple(self): return self.has_multiple_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_meaning()): self.set_meaning(x.meaning()) + if (x.has_meaning_uri()): self.set_meaning_uri(x.meaning_uri()) + if (x.has_name()): self.set_name(x.name()) + if (x.has_value()): self.mutable_value().MergeFrom(x.value()) + if (x.has_multiple()): self.set_multiple(x.multiple()) + + def Equals(self, x): + if x is self: return 1 + if self.has_meaning_ != x.has_meaning_: return 0 + if self.has_meaning_ and self.meaning_ != x.meaning_: return 0 + if self.has_meaning_uri_ != x.has_meaning_uri_: return 0 + if self.has_meaning_uri_ and self.meaning_uri_ != x.meaning_uri_: return 0 + if self.has_name_ != x.has_name_: return 0 + if self.has_name_ and self.name_ != x.name_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + if self.has_multiple_ != x.has_multiple_: return 0 + if self.has_multiple_ and self.multiple_ != x.multiple_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: name not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + elif not self.value_.IsInitialized(debug_strs): initialized = 0 + if (not self.has_multiple_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: multiple not set.') + return initialized + + def ByteSize(self): + n = 0 + if (self.has_meaning_): n += 1 + self.lengthVarInt64(self.meaning_) + if (self.has_meaning_uri_): n += 1 + self.lengthString(len(self.meaning_uri_)) + n += self.lengthString(len(self.name_)) + n += self.lengthString(self.value_.ByteSize()) + return n + 4 + + def Clear(self): + self.clear_meaning() + self.clear_meaning_uri() + self.clear_name() + self.clear_value() + self.clear_multiple() + + def OutputUnchecked(self, out): + if (self.has_meaning_): + out.putVarInt32(8) + out.putVarInt32(self.meaning_) + if (self.has_meaning_uri_): + out.putVarInt32(18) + out.putPrefixedString(self.meaning_uri_) + out.putVarInt32(26) + out.putPrefixedString(self.name_) + out.putVarInt32(32) + out.putBoolean(self.multiple_) + out.putVarInt32(42) + out.putVarInt32(self.value_.ByteSize()) + self.value_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_meaning(d.getVarInt32()) + continue + if tt == 18: + self.set_meaning_uri(d.getPrefixedString()) + continue + if tt == 26: + self.set_name(d.getPrefixedString()) + continue + if tt == 32: + self.set_multiple(d.getBoolean()) + continue + if tt == 42: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_value().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_meaning_: res+=prefix+("meaning: %s\n" % self.DebugFormatInt32(self.meaning_)) + if self.has_meaning_uri_: res+=prefix+("meaning_uri: %s\n" % self.DebugFormatString(self.meaning_uri_)) + if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_)) + if self.has_value_: + res+=prefix+"value <\n" + res+=self.value_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_multiple_: res+=prefix+("multiple: %s\n" % self.DebugFormatBool(self.multiple_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kmeaning = 1 + kmeaning_uri = 2 + kname = 3 + kvalue = 5 + kmultiple = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "meaning", + 2: "meaning_uri", + 3: "name", + 4: "multiple", + 5: "value", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Path_Element(ProtocolBuffer.ProtocolMessage): + has_type_ = 0 + type_ = "" + has_id_ = 0 + id_ = 0 + has_name_ = 0 + name_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def type(self): return self.type_ + + def set_type(self, x): + self.has_type_ = 1 + self.type_ = x + + def clear_type(self): + if self.has_type_: + self.has_type_ = 0 + self.type_ = "" + + def has_type(self): return self.has_type_ + + def id(self): return self.id_ + + def set_id(self, x): + self.has_id_ = 1 + self.id_ = x + + def clear_id(self): + if self.has_id_: + self.has_id_ = 0 + self.id_ = 0 + + def has_id(self): return self.has_id_ + + def name(self): return self.name_ + + def set_name(self, x): + self.has_name_ = 1 + self.name_ = x + + def clear_name(self): + if self.has_name_: + self.has_name_ = 0 + self.name_ = "" + + def has_name(self): return self.has_name_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_type()): self.set_type(x.type()) + if (x.has_id()): self.set_id(x.id()) + if (x.has_name()): self.set_name(x.name()) + + def Equals(self, x): + if x is self: return 1 + if self.has_type_ != x.has_type_: return 0 + if self.has_type_ and self.type_ != x.type_: return 0 + if self.has_id_ != x.has_id_: return 0 + if self.has_id_ and self.id_ != x.id_: return 0 + if self.has_name_ != x.has_name_: return 0 + if self.has_name_ and self.name_ != x.name_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_type_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: type not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.type_)) + if (self.has_id_): n += 1 + self.lengthVarInt64(self.id_) + if (self.has_name_): n += 1 + self.lengthString(len(self.name_)) + return n + 1 + + def Clear(self): + self.clear_type() + self.clear_id() + self.clear_name() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.type_) + if (self.has_id_): + out.putVarInt32(24) + out.putVarInt64(self.id_) + if (self.has_name_): + out.putVarInt32(34) + out.putPrefixedString(self.name_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + self.set_type(d.getPrefixedString()) + continue + if tt == 24: + self.set_id(d.getVarInt64()) + continue + if tt == 34: + self.set_name(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_type_: res+=prefix+("type: %s\n" % self.DebugFormatString(self.type_)) + if self.has_id_: res+=prefix+("id: %s\n" % self.DebugFormatInt64(self.id_)) + if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_)) + return res + +class Path(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.element_ = [] + if contents is not None: self.MergeFromString(contents) + + def element_size(self): return len(self.element_) + def element_list(self): return self.element_ + + def element(self, i): + return self.element_[i] + + def mutable_element(self, i): + return self.element_[i] + + def add_element(self): + x = Path_Element() + self.element_.append(x) + return x + + def clear_element(self): + self.element_ = [] + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.element_size()): self.add_element().CopyFrom(x.element(i)) + + def Equals(self, x): + if x is self: return 1 + if len(self.element_) != len(x.element_): return 0 + for e1, e2 in zip(self.element_, x.element_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.element_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.element_) + for i in xrange(len(self.element_)): n += self.element_[i].ByteSize() + return n + 0 + + def Clear(self): + self.clear_element() + + def OutputUnchecked(self, out): + for i in xrange(len(self.element_)): + out.putVarInt32(11) + self.element_[i].OutputUnchecked(out) + out.putVarInt32(12) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_element().TryMerge(d) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.element_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Element%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kElementGroup = 1 + kElementtype = 2 + kElementid = 3 + kElementname = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Element", + 2: "type", + 3: "id", + 4: "name", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Reference(ProtocolBuffer.ProtocolMessage): + has_app_ = 0 + app_ = "" + has_name_space_ = 0 + name_space_ = "" + has_path_ = 0 + + def __init__(self, contents=None): + self.path_ = Path() + if contents is not None: self.MergeFromString(contents) + + def app(self): return self.app_ + + def set_app(self, x): + self.has_app_ = 1 + self.app_ = x + + def clear_app(self): + if self.has_app_: + self.has_app_ = 0 + self.app_ = "" + + def has_app(self): return self.has_app_ + + def name_space(self): return self.name_space_ + + def set_name_space(self, x): + self.has_name_space_ = 1 + self.name_space_ = x + + def clear_name_space(self): + if self.has_name_space_: + self.has_name_space_ = 0 + self.name_space_ = "" + + def has_name_space(self): return self.has_name_space_ + + def path(self): return self.path_ + + def mutable_path(self): self.has_path_ = 1; return self.path_ + + def clear_path(self):self.has_path_ = 0; self.path_.Clear() + + def has_path(self): return self.has_path_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app()): self.set_app(x.app()) + if (x.has_name_space()): self.set_name_space(x.name_space()) + if (x.has_path()): self.mutable_path().MergeFrom(x.path()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_ != x.has_app_: return 0 + if self.has_app_ and self.app_ != x.app_: return 0 + if self.has_name_space_ != x.has_name_space_: return 0 + if self.has_name_space_ and self.name_space_ != x.name_space_: return 0 + if self.has_path_ != x.has_path_: return 0 + if self.has_path_ and self.path_ != x.path_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app not set.') + if (not self.has_path_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: path not set.') + elif not self.path_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_)) + if (self.has_name_space_): n += 2 + self.lengthString(len(self.name_space_)) + n += self.lengthString(self.path_.ByteSize()) + return n + 2 + + def Clear(self): + self.clear_app() + self.clear_name_space() + self.clear_path() + + def OutputUnchecked(self, out): + out.putVarInt32(106) + out.putPrefixedString(self.app_) + out.putVarInt32(114) + out.putVarInt32(self.path_.ByteSize()) + self.path_.OutputUnchecked(out) + if (self.has_name_space_): + out.putVarInt32(162) + out.putPrefixedString(self.name_space_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 106: + self.set_app(d.getPrefixedString()) + continue + if tt == 114: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_path().TryMerge(tmp) + continue + if tt == 162: + self.set_name_space(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_: res+=prefix+("app: %s\n" % self.DebugFormatString(self.app_)) + if self.has_name_space_: res+=prefix+("name_space: %s\n" % self.DebugFormatString(self.name_space_)) + if self.has_path_: + res+=prefix+"path <\n" + res+=self.path_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp = 13 + kname_space = 20 + kpath = 14 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 13: "app", + 14: "path", + 20: "name_space", + }, 20) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 13: ProtocolBuffer.Encoder.STRING, + 14: ProtocolBuffer.Encoder.STRING, + 20: ProtocolBuffer.Encoder.STRING, + }, 20, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class User(ProtocolBuffer.ProtocolMessage): + has_email_ = 0 + email_ = "" + has_auth_domain_ = 0 + auth_domain_ = "" + has_nickname_ = 0 + nickname_ = "" + has_gaiaid_ = 0 + gaiaid_ = 0 + has_obfuscated_gaiaid_ = 0 + obfuscated_gaiaid_ = "" + has_federated_identity_ = 0 + federated_identity_ = "" + has_federated_provider_ = 0 + federated_provider_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def email(self): return self.email_ + + def set_email(self, x): + self.has_email_ = 1 + self.email_ = x + + def clear_email(self): + if self.has_email_: + self.has_email_ = 0 + self.email_ = "" + + def has_email(self): return self.has_email_ + + def auth_domain(self): return self.auth_domain_ + + def set_auth_domain(self, x): + self.has_auth_domain_ = 1 + self.auth_domain_ = x + + def clear_auth_domain(self): + if self.has_auth_domain_: + self.has_auth_domain_ = 0 + self.auth_domain_ = "" + + def has_auth_domain(self): return self.has_auth_domain_ + + def nickname(self): return self.nickname_ + + def set_nickname(self, x): + self.has_nickname_ = 1 + self.nickname_ = x + + def clear_nickname(self): + if self.has_nickname_: + self.has_nickname_ = 0 + self.nickname_ = "" + + def has_nickname(self): return self.has_nickname_ + + def gaiaid(self): return self.gaiaid_ + + def set_gaiaid(self, x): + self.has_gaiaid_ = 1 + self.gaiaid_ = x + + def clear_gaiaid(self): + if self.has_gaiaid_: + self.has_gaiaid_ = 0 + self.gaiaid_ = 0 + + def has_gaiaid(self): return self.has_gaiaid_ + + def obfuscated_gaiaid(self): return self.obfuscated_gaiaid_ + + def set_obfuscated_gaiaid(self, x): + self.has_obfuscated_gaiaid_ = 1 + self.obfuscated_gaiaid_ = x + + def clear_obfuscated_gaiaid(self): + if self.has_obfuscated_gaiaid_: + self.has_obfuscated_gaiaid_ = 0 + self.obfuscated_gaiaid_ = "" + + def has_obfuscated_gaiaid(self): return self.has_obfuscated_gaiaid_ + + def federated_identity(self): return self.federated_identity_ + + def set_federated_identity(self, x): + self.has_federated_identity_ = 1 + self.federated_identity_ = x + + def clear_federated_identity(self): + if self.has_federated_identity_: + self.has_federated_identity_ = 0 + self.federated_identity_ = "" + + def has_federated_identity(self): return self.has_federated_identity_ + + def federated_provider(self): return self.federated_provider_ + + def set_federated_provider(self, x): + self.has_federated_provider_ = 1 + self.federated_provider_ = x + + def clear_federated_provider(self): + if self.has_federated_provider_: + self.has_federated_provider_ = 0 + self.federated_provider_ = "" + + def has_federated_provider(self): return self.has_federated_provider_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_email()): self.set_email(x.email()) + if (x.has_auth_domain()): self.set_auth_domain(x.auth_domain()) + if (x.has_nickname()): self.set_nickname(x.nickname()) + if (x.has_gaiaid()): self.set_gaiaid(x.gaiaid()) + if (x.has_obfuscated_gaiaid()): self.set_obfuscated_gaiaid(x.obfuscated_gaiaid()) + if (x.has_federated_identity()): self.set_federated_identity(x.federated_identity()) + if (x.has_federated_provider()): self.set_federated_provider(x.federated_provider()) + + def Equals(self, x): + if x is self: return 1 + if self.has_email_ != x.has_email_: return 0 + if self.has_email_ and self.email_ != x.email_: return 0 + if self.has_auth_domain_ != x.has_auth_domain_: return 0 + if self.has_auth_domain_ and self.auth_domain_ != x.auth_domain_: return 0 + if self.has_nickname_ != x.has_nickname_: return 0 + if self.has_nickname_ and self.nickname_ != x.nickname_: return 0 + if self.has_gaiaid_ != x.has_gaiaid_: return 0 + if self.has_gaiaid_ and self.gaiaid_ != x.gaiaid_: return 0 + if self.has_obfuscated_gaiaid_ != x.has_obfuscated_gaiaid_: return 0 + if self.has_obfuscated_gaiaid_ and self.obfuscated_gaiaid_ != x.obfuscated_gaiaid_: return 0 + if self.has_federated_identity_ != x.has_federated_identity_: return 0 + if self.has_federated_identity_ and self.federated_identity_ != x.federated_identity_: return 0 + if self.has_federated_provider_ != x.has_federated_provider_: return 0 + if self.has_federated_provider_ and self.federated_provider_ != x.federated_provider_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_email_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: email not set.') + if (not self.has_auth_domain_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: auth_domain not set.') + if (not self.has_gaiaid_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: gaiaid not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.email_)) + n += self.lengthString(len(self.auth_domain_)) + if (self.has_nickname_): n += 1 + self.lengthString(len(self.nickname_)) + n += self.lengthVarInt64(self.gaiaid_) + if (self.has_obfuscated_gaiaid_): n += 1 + self.lengthString(len(self.obfuscated_gaiaid_)) + if (self.has_federated_identity_): n += 1 + self.lengthString(len(self.federated_identity_)) + if (self.has_federated_provider_): n += 1 + self.lengthString(len(self.federated_provider_)) + return n + 3 + + def Clear(self): + self.clear_email() + self.clear_auth_domain() + self.clear_nickname() + self.clear_gaiaid() + self.clear_obfuscated_gaiaid() + self.clear_federated_identity() + self.clear_federated_provider() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.email_) + out.putVarInt32(18) + out.putPrefixedString(self.auth_domain_) + if (self.has_nickname_): + out.putVarInt32(26) + out.putPrefixedString(self.nickname_) + out.putVarInt32(32) + out.putVarInt64(self.gaiaid_) + if (self.has_obfuscated_gaiaid_): + out.putVarInt32(42) + out.putPrefixedString(self.obfuscated_gaiaid_) + if (self.has_federated_identity_): + out.putVarInt32(50) + out.putPrefixedString(self.federated_identity_) + if (self.has_federated_provider_): + out.putVarInt32(58) + out.putPrefixedString(self.federated_provider_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_email(d.getPrefixedString()) + continue + if tt == 18: + self.set_auth_domain(d.getPrefixedString()) + continue + if tt == 26: + self.set_nickname(d.getPrefixedString()) + continue + if tt == 32: + self.set_gaiaid(d.getVarInt64()) + continue + if tt == 42: + self.set_obfuscated_gaiaid(d.getPrefixedString()) + continue + if tt == 50: + self.set_federated_identity(d.getPrefixedString()) + continue + if tt == 58: + self.set_federated_provider(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_email_: res+=prefix+("email: %s\n" % self.DebugFormatString(self.email_)) + if self.has_auth_domain_: res+=prefix+("auth_domain: %s\n" % self.DebugFormatString(self.auth_domain_)) + if self.has_nickname_: res+=prefix+("nickname: %s\n" % self.DebugFormatString(self.nickname_)) + if self.has_gaiaid_: res+=prefix+("gaiaid: %s\n" % self.DebugFormatInt64(self.gaiaid_)) + if self.has_obfuscated_gaiaid_: res+=prefix+("obfuscated_gaiaid: %s\n" % self.DebugFormatString(self.obfuscated_gaiaid_)) + if self.has_federated_identity_: res+=prefix+("federated_identity: %s\n" % self.DebugFormatString(self.federated_identity_)) + if self.has_federated_provider_: res+=prefix+("federated_provider: %s\n" % self.DebugFormatString(self.federated_provider_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kemail = 1 + kauth_domain = 2 + knickname = 3 + kgaiaid = 4 + kobfuscated_gaiaid = 5 + kfederated_identity = 6 + kfederated_provider = 7 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "email", + 2: "auth_domain", + 3: "nickname", + 4: "gaiaid", + 5: "obfuscated_gaiaid", + 6: "federated_identity", + 7: "federated_provider", + }, 7) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 6: ProtocolBuffer.Encoder.STRING, + 7: ProtocolBuffer.Encoder.STRING, + }, 7, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class EntityProto(ProtocolBuffer.ProtocolMessage): + + GD_CONTACT = 1 + GD_EVENT = 2 + GD_MESSAGE = 3 + + _Kind_NAMES = { + 1: "GD_CONTACT", + 2: "GD_EVENT", + 3: "GD_MESSAGE", + } + + def Kind_Name(cls, x): return cls._Kind_NAMES.get(x, "") + Kind_Name = classmethod(Kind_Name) + + has_key_ = 0 + has_entity_group_ = 0 + has_owner_ = 0 + owner_ = None + has_kind_ = 0 + kind_ = 0 + has_kind_uri_ = 0 + kind_uri_ = "" + + def __init__(self, contents=None): + self.key_ = Reference() + self.entity_group_ = Path() + self.property_ = [] + self.raw_property_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def mutable_key(self): self.has_key_ = 1; return self.key_ + + def clear_key(self):self.has_key_ = 0; self.key_.Clear() + + def has_key(self): return self.has_key_ + + def entity_group(self): return self.entity_group_ + + def mutable_entity_group(self): self.has_entity_group_ = 1; return self.entity_group_ + + def clear_entity_group(self):self.has_entity_group_ = 0; self.entity_group_.Clear() + + def has_entity_group(self): return self.has_entity_group_ + + def owner(self): + if self.owner_ is None: + self.lazy_init_lock_.acquire() + try: + if self.owner_ is None: self.owner_ = User() + finally: + self.lazy_init_lock_.release() + return self.owner_ + + def mutable_owner(self): self.has_owner_ = 1; return self.owner() + + def clear_owner(self): + if self.has_owner_: + self.has_owner_ = 0; + if self.owner_ is not None: self.owner_.Clear() + + def has_owner(self): return self.has_owner_ + + def kind(self): return self.kind_ + + def set_kind(self, x): + self.has_kind_ = 1 + self.kind_ = x + + def clear_kind(self): + if self.has_kind_: + self.has_kind_ = 0 + self.kind_ = 0 + + def has_kind(self): return self.has_kind_ + + def kind_uri(self): return self.kind_uri_ + + def set_kind_uri(self, x): + self.has_kind_uri_ = 1 + self.kind_uri_ = x + + def clear_kind_uri(self): + if self.has_kind_uri_: + self.has_kind_uri_ = 0 + self.kind_uri_ = "" + + def has_kind_uri(self): return self.has_kind_uri_ + + def property_size(self): return len(self.property_) + def property_list(self): return self.property_ + + def property(self, i): + return self.property_[i] + + def mutable_property(self, i): + return self.property_[i] + + def add_property(self): + x = Property() + self.property_.append(x) + return x + + def clear_property(self): + self.property_ = [] + def raw_property_size(self): return len(self.raw_property_) + def raw_property_list(self): return self.raw_property_ + + def raw_property(self, i): + return self.raw_property_[i] + + def mutable_raw_property(self, i): + return self.raw_property_[i] + + def add_raw_property(self): + x = Property() + self.raw_property_.append(x) + return x + + def clear_raw_property(self): + self.raw_property_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.mutable_key().MergeFrom(x.key()) + if (x.has_entity_group()): self.mutable_entity_group().MergeFrom(x.entity_group()) + if (x.has_owner()): self.mutable_owner().MergeFrom(x.owner()) + if (x.has_kind()): self.set_kind(x.kind()) + if (x.has_kind_uri()): self.set_kind_uri(x.kind_uri()) + for i in xrange(x.property_size()): self.add_property().CopyFrom(x.property(i)) + for i in xrange(x.raw_property_size()): self.add_raw_property().CopyFrom(x.raw_property(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_entity_group_ != x.has_entity_group_: return 0 + if self.has_entity_group_ and self.entity_group_ != x.entity_group_: return 0 + if self.has_owner_ != x.has_owner_: return 0 + if self.has_owner_ and self.owner_ != x.owner_: return 0 + if self.has_kind_ != x.has_kind_: return 0 + if self.has_kind_ and self.kind_ != x.kind_: return 0 + if self.has_kind_uri_ != x.has_kind_uri_: return 0 + if self.has_kind_uri_ and self.kind_uri_ != x.kind_uri_: return 0 + if len(self.property_) != len(x.property_): return 0 + for e1, e2 in zip(self.property_, x.property_): + if e1 != e2: return 0 + if len(self.raw_property_) != len(x.raw_property_): return 0 + for e1, e2 in zip(self.raw_property_, x.raw_property_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + elif not self.key_.IsInitialized(debug_strs): initialized = 0 + if (not self.has_entity_group_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: entity_group not set.') + elif not self.entity_group_.IsInitialized(debug_strs): initialized = 0 + if (self.has_owner_ and not self.owner_.IsInitialized(debug_strs)): initialized = 0 + for p in self.property_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.raw_property_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.key_.ByteSize()) + n += self.lengthString(self.entity_group_.ByteSize()) + if (self.has_owner_): n += 2 + self.lengthString(self.owner_.ByteSize()) + if (self.has_kind_): n += 1 + self.lengthVarInt64(self.kind_) + if (self.has_kind_uri_): n += 1 + self.lengthString(len(self.kind_uri_)) + n += 1 * len(self.property_) + for i in xrange(len(self.property_)): n += self.lengthString(self.property_[i].ByteSize()) + n += 1 * len(self.raw_property_) + for i in xrange(len(self.raw_property_)): n += self.lengthString(self.raw_property_[i].ByteSize()) + return n + 3 + + def Clear(self): + self.clear_key() + self.clear_entity_group() + self.clear_owner() + self.clear_kind() + self.clear_kind_uri() + self.clear_property() + self.clear_raw_property() + + def OutputUnchecked(self, out): + if (self.has_kind_): + out.putVarInt32(32) + out.putVarInt32(self.kind_) + if (self.has_kind_uri_): + out.putVarInt32(42) + out.putPrefixedString(self.kind_uri_) + out.putVarInt32(106) + out.putVarInt32(self.key_.ByteSize()) + self.key_.OutputUnchecked(out) + for i in xrange(len(self.property_)): + out.putVarInt32(114) + out.putVarInt32(self.property_[i].ByteSize()) + self.property_[i].OutputUnchecked(out) + for i in xrange(len(self.raw_property_)): + out.putVarInt32(122) + out.putVarInt32(self.raw_property_[i].ByteSize()) + self.raw_property_[i].OutputUnchecked(out) + out.putVarInt32(130) + out.putVarInt32(self.entity_group_.ByteSize()) + self.entity_group_.OutputUnchecked(out) + if (self.has_owner_): + out.putVarInt32(138) + out.putVarInt32(self.owner_.ByteSize()) + self.owner_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 32: + self.set_kind(d.getVarInt32()) + continue + if tt == 42: + self.set_kind_uri(d.getPrefixedString()) + continue + if tt == 106: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_key().TryMerge(tmp) + continue + if tt == 114: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_property().TryMerge(tmp) + continue + if tt == 122: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_raw_property().TryMerge(tmp) + continue + if tt == 130: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_entity_group().TryMerge(tmp) + continue + if tt == 138: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_owner().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: + res+=prefix+"key <\n" + res+=self.key_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_entity_group_: + res+=prefix+"entity_group <\n" + res+=self.entity_group_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_owner_: + res+=prefix+"owner <\n" + res+=self.owner_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_kind_: res+=prefix+("kind: %s\n" % self.DebugFormatInt32(self.kind_)) + if self.has_kind_uri_: res+=prefix+("kind_uri: %s\n" % self.DebugFormatString(self.kind_uri_)) + cnt=0 + for e in self.property_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("property%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + cnt=0 + for e in self.raw_property_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("raw_property%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 13 + kentity_group = 16 + kowner = 17 + kkind = 4 + kkind_uri = 5 + kproperty = 14 + kraw_property = 15 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 4: "kind", + 5: "kind_uri", + 13: "key", + 14: "property", + 15: "raw_property", + 16: "entity_group", + 17: "owner", + }, 17) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.STRING, + 13: ProtocolBuffer.Encoder.STRING, + 14: ProtocolBuffer.Encoder.STRING, + 15: ProtocolBuffer.Encoder.STRING, + 16: ProtocolBuffer.Encoder.STRING, + 17: ProtocolBuffer.Encoder.STRING, + }, 17, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompositeProperty(ProtocolBuffer.ProtocolMessage): + has_index_id_ = 0 + index_id_ = 0 + + def __init__(self, contents=None): + self.value_ = [] + if contents is not None: self.MergeFromString(contents) + + def index_id(self): return self.index_id_ + + def set_index_id(self, x): + self.has_index_id_ = 1 + self.index_id_ = x + + def clear_index_id(self): + if self.has_index_id_: + self.has_index_id_ = 0 + self.index_id_ = 0 + + def has_index_id(self): return self.has_index_id_ + + def value_size(self): return len(self.value_) + def value_list(self): return self.value_ + + def value(self, i): + return self.value_[i] + + def set_value(self, i, x): + self.value_[i] = x + + def add_value(self, x): + self.value_.append(x) + + def clear_value(self): + self.value_ = [] + + + def MergeFrom(self, x): + assert x is not self + if (x.has_index_id()): self.set_index_id(x.index_id()) + for i in xrange(x.value_size()): self.add_value(x.value(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_index_id_ != x.has_index_id_: return 0 + if self.has_index_id_ and self.index_id_ != x.index_id_: return 0 + if len(self.value_) != len(x.value_): return 0 + for e1, e2 in zip(self.value_, x.value_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_index_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: index_id not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.index_id_) + n += 1 * len(self.value_) + for i in xrange(len(self.value_)): n += self.lengthString(len(self.value_[i])) + return n + 1 + + def Clear(self): + self.clear_index_id() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.index_id_) + for i in xrange(len(self.value_)): + out.putVarInt32(18) + out.putPrefixedString(self.value_[i]) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_index_id(d.getVarInt64()) + continue + if tt == 18: + self.add_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_index_id_: res+=prefix+("index_id: %s\n" % self.DebugFormatInt64(self.index_id_)) + cnt=0 + for e in self.value_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("value%s: %s\n" % (elm, self.DebugFormatString(e))) + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kindex_id = 1 + kvalue = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "index_id", + 2: "value", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Index_Property(ProtocolBuffer.ProtocolMessage): + + ASCENDING = 1 + DESCENDING = 2 + + _Direction_NAMES = { + 1: "ASCENDING", + 2: "DESCENDING", + } + + def Direction_Name(cls, x): return cls._Direction_NAMES.get(x, "") + Direction_Name = classmethod(Direction_Name) + + has_name_ = 0 + name_ = "" + has_direction_ = 0 + direction_ = 1 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def name(self): return self.name_ + + def set_name(self, x): + self.has_name_ = 1 + self.name_ = x + + def clear_name(self): + if self.has_name_: + self.has_name_ = 0 + self.name_ = "" + + def has_name(self): return self.has_name_ + + def direction(self): return self.direction_ + + def set_direction(self, x): + self.has_direction_ = 1 + self.direction_ = x + + def clear_direction(self): + if self.has_direction_: + self.has_direction_ = 0 + self.direction_ = 1 + + def has_direction(self): return self.has_direction_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_name()): self.set_name(x.name()) + if (x.has_direction()): self.set_direction(x.direction()) + + def Equals(self, x): + if x is self: return 1 + if self.has_name_ != x.has_name_: return 0 + if self.has_name_ and self.name_ != x.name_: return 0 + if self.has_direction_ != x.has_direction_: return 0 + if self.has_direction_ and self.direction_ != x.direction_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: name not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.name_)) + if (self.has_direction_): n += 1 + self.lengthVarInt64(self.direction_) + return n + 1 + + def Clear(self): + self.clear_name() + self.clear_direction() + + def OutputUnchecked(self, out): + out.putVarInt32(26) + out.putPrefixedString(self.name_) + if (self.has_direction_): + out.putVarInt32(32) + out.putVarInt32(self.direction_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 20: break + if tt == 26: + self.set_name(d.getPrefixedString()) + continue + if tt == 32: + self.set_direction(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_)) + if self.has_direction_: res+=prefix+("direction: %s\n" % self.DebugFormatInt32(self.direction_)) + return res + +class Index(ProtocolBuffer.ProtocolMessage): + has_entity_type_ = 0 + entity_type_ = "" + has_ancestor_ = 0 + ancestor_ = 0 + + def __init__(self, contents=None): + self.property_ = [] + if contents is not None: self.MergeFromString(contents) + + def entity_type(self): return self.entity_type_ + + def set_entity_type(self, x): + self.has_entity_type_ = 1 + self.entity_type_ = x + + def clear_entity_type(self): + if self.has_entity_type_: + self.has_entity_type_ = 0 + self.entity_type_ = "" + + def has_entity_type(self): return self.has_entity_type_ + + def ancestor(self): return self.ancestor_ + + def set_ancestor(self, x): + self.has_ancestor_ = 1 + self.ancestor_ = x + + def clear_ancestor(self): + if self.has_ancestor_: + self.has_ancestor_ = 0 + self.ancestor_ = 0 + + def has_ancestor(self): return self.has_ancestor_ + + def property_size(self): return len(self.property_) + def property_list(self): return self.property_ + + def property(self, i): + return self.property_[i] + + def mutable_property(self, i): + return self.property_[i] + + def add_property(self): + x = Index_Property() + self.property_.append(x) + return x + + def clear_property(self): + self.property_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_entity_type()): self.set_entity_type(x.entity_type()) + if (x.has_ancestor()): self.set_ancestor(x.ancestor()) + for i in xrange(x.property_size()): self.add_property().CopyFrom(x.property(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_entity_type_ != x.has_entity_type_: return 0 + if self.has_entity_type_ and self.entity_type_ != x.entity_type_: return 0 + if self.has_ancestor_ != x.has_ancestor_: return 0 + if self.has_ancestor_ and self.ancestor_ != x.ancestor_: return 0 + if len(self.property_) != len(x.property_): return 0 + for e1, e2 in zip(self.property_, x.property_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_entity_type_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: entity_type not set.') + if (not self.has_ancestor_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: ancestor not set.') + for p in self.property_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.entity_type_)) + n += 2 * len(self.property_) + for i in xrange(len(self.property_)): n += self.property_[i].ByteSize() + return n + 3 + + def Clear(self): + self.clear_entity_type() + self.clear_ancestor() + self.clear_property() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.entity_type_) + for i in xrange(len(self.property_)): + out.putVarInt32(19) + self.property_[i].OutputUnchecked(out) + out.putVarInt32(20) + out.putVarInt32(40) + out.putBoolean(self.ancestor_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_entity_type(d.getPrefixedString()) + continue + if tt == 19: + self.add_property().TryMerge(d) + continue + if tt == 40: + self.set_ancestor(d.getBoolean()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_entity_type_: res+=prefix+("entity_type: %s\n" % self.DebugFormatString(self.entity_type_)) + if self.has_ancestor_: res+=prefix+("ancestor: %s\n" % self.DebugFormatBool(self.ancestor_)) + cnt=0 + for e in self.property_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Property%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kentity_type = 1 + kancestor = 5 + kPropertyGroup = 2 + kPropertyname = 3 + kPropertydirection = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "entity_type", + 2: "Property", + 3: "name", + 4: "direction", + 5: "ancestor", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STARTGROUP, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + 5: ProtocolBuffer.Encoder.NUMERIC, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class CompositeIndex(ProtocolBuffer.ProtocolMessage): + + WRITE_ONLY = 1 + READ_WRITE = 2 + DELETED = 3 + ERROR = 4 + + _State_NAMES = { + 1: "WRITE_ONLY", + 2: "READ_WRITE", + 3: "DELETED", + 4: "ERROR", + } + + def State_Name(cls, x): return cls._State_NAMES.get(x, "") + State_Name = classmethod(State_Name) + + has_app_id_ = 0 + app_id_ = "" + has_id_ = 0 + id_ = 0 + has_definition_ = 0 + has_state_ = 0 + state_ = 0 + + def __init__(self, contents=None): + self.definition_ = Index() + if contents is not None: self.MergeFromString(contents) + + def app_id(self): return self.app_id_ + + def set_app_id(self, x): + self.has_app_id_ = 1 + self.app_id_ = x + + def clear_app_id(self): + if self.has_app_id_: + self.has_app_id_ = 0 + self.app_id_ = "" + + def has_app_id(self): return self.has_app_id_ + + def id(self): return self.id_ + + def set_id(self, x): + self.has_id_ = 1 + self.id_ = x + + def clear_id(self): + if self.has_id_: + self.has_id_ = 0 + self.id_ = 0 + + def has_id(self): return self.has_id_ + + def definition(self): return self.definition_ + + def mutable_definition(self): self.has_definition_ = 1; return self.definition_ + + def clear_definition(self):self.has_definition_ = 0; self.definition_.Clear() + + def has_definition(self): return self.has_definition_ + + def state(self): return self.state_ + + def set_state(self, x): + self.has_state_ = 1 + self.state_ = x + + def clear_state(self): + if self.has_state_: + self.has_state_ = 0 + self.state_ = 0 + + def has_state(self): return self.has_state_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_app_id()): self.set_app_id(x.app_id()) + if (x.has_id()): self.set_id(x.id()) + if (x.has_definition()): self.mutable_definition().MergeFrom(x.definition()) + if (x.has_state()): self.set_state(x.state()) + + def Equals(self, x): + if x is self: return 1 + if self.has_app_id_ != x.has_app_id_: return 0 + if self.has_app_id_ and self.app_id_ != x.app_id_: return 0 + if self.has_id_ != x.has_id_: return 0 + if self.has_id_ and self.id_ != x.id_: return 0 + if self.has_definition_ != x.has_definition_: return 0 + if self.has_definition_ and self.definition_ != x.definition_: return 0 + if self.has_state_ != x.has_state_: return 0 + if self.has_state_ and self.state_ != x.state_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_app_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: app_id not set.') + if (not self.has_id_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: id not set.') + if (not self.has_definition_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: definition not set.') + elif not self.definition_.IsInitialized(debug_strs): initialized = 0 + if (not self.has_state_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: state not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.app_id_)) + n += self.lengthVarInt64(self.id_) + n += self.lengthString(self.definition_.ByteSize()) + n += self.lengthVarInt64(self.state_) + return n + 4 + + def Clear(self): + self.clear_app_id() + self.clear_id() + self.clear_definition() + self.clear_state() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.app_id_) + out.putVarInt32(16) + out.putVarInt64(self.id_) + out.putVarInt32(26) + out.putVarInt32(self.definition_.ByteSize()) + self.definition_.OutputUnchecked(out) + out.putVarInt32(32) + out.putVarInt32(self.state_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_app_id(d.getPrefixedString()) + continue + if tt == 16: + self.set_id(d.getVarInt64()) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_definition().TryMerge(tmp) + continue + if tt == 32: + self.set_state(d.getVarInt32()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_app_id_: res+=prefix+("app_id: %s\n" % self.DebugFormatString(self.app_id_)) + if self.has_id_: res+=prefix+("id: %s\n" % self.DebugFormatInt64(self.id_)) + if self.has_definition_: + res+=prefix+"definition <\n" + res+=self.definition_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_state_: res+=prefix+("state: %s\n" % self.DebugFormatInt32(self.state_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kapp_id = 1 + kid = 2 + kdefinition = 3 + kstate = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "app_id", + 2: "id", + 3: "definition", + 4: "state", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.NUMERIC, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['PropertyValue','PropertyValue_ReferenceValuePathElement','PropertyValue_PointValue','PropertyValue_UserValue','PropertyValue_ReferenceValue','Property','Path','Path_Element','Reference','User','EntityProto','CompositeProperty','Index','Index_Property','CompositeIndex'] diff --git a/google_appengine/google/appengine/datastore/entity_pb.pyc b/google_appengine/google/appengine/datastore/entity_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5a0bbe507d69a82c8619711d929f5cba58f05ad GIT binary patch literal 126396 zcwX(j33yz`btYOD-Dse(0|W_z1SxP6Nl`ng)?!H(Nk9}W38b5pWWk~#2)9YL2m&C` zkU@!x7btd~<27CqCyH0aiDM<>B#sj&wqwV3>~WGwGW(KwZ@zq!eDA$vzGSkzH_2q2 zSN~tPmb)~72GJMHb3}FFE_LrcRdvp(Q)jC`_{;W|KN#>InGnH$J@WT}{LTHLCxkEm z_rwJuCOt9g2@mHD;zENMZ4kk6MvP{KFUv;ZXGO^q<(xR{3BOUiB*dZ+PmVSTKj++N zGB=uq-|XDTn;UuIw>URi&5ah}w>dZ3&5c&!cQ`jX&5btUcR4q@&5d^9_c%8;m>V6! z?{#i$G&eehzsb4LXKr)}f3tI=-`waH{ubxPR&%3A_yf+3ZRW-X;cs_tTxV|d3V+bK zvBTWhh%S*mFZ)>brtCM_5wc~n)v_59U~ z$&nV74q2R>ovutzOivz}KYO+`J2EFnUR)esm@QT2XQ$@wyLC{WJTp5!HF3U#v#IHu zCdMZx%Twh_%V7M!`KkH2l0P+mQC6OrEl*X3CrcMc<}W@|n!V94Ju`o9Y_2jphquhl zlqSmKla;*+hMCfAlk=sqVo3s0nwlu_^yGNuJYG_osu+aL$lnnl?BB?uB!H(9 zHG!ZKXEQv`vTS6TNH$~SaIrr?$k8nrMI3AmFmZGM@XLnC zp?yix;Wkk`wQn;<5`9rYG0`9k{X2@D!AM`>Q_tqbbhq%X$od{GElQ%EQiOeqXfU!5 zPgSxwoSrIGI_1HmrP*_(2WO`*o~p=EJZEdnl_t+tn(@i$sfrwvxqbLPi@6b5+;V>U zV(FHtiP`b#TZX3JJT*Bz?$6zFZhHFMWNB=CW~MZCt~^x=F5F@-_~VuFxytlx=@$9Y za^>>a%riI7T;`X?#(-^OV>|E-z#zPKF#*W+k1cLOi`gl~T3YG~^Q zayIzjZoD5Ki4V>uK_)Aq9pfnBfMa!v9X(P8sH)HNvN%6JH^wFuoAq_Jqk?J+^=%El zI9ICh`_~II0C~?0fN2#PtOj6ulSD7wK1p(_eFo_=Y$M)NY%mRKLBaN=G*0;7#AIoF zmTkKMU&*m;EpF?UK`(-x7R2ZsjSII3ja@5*w1QqQ`>ByZ13!100rP5zpebm$1i#p+ zud)rr*19(YNC65c`v=9U@ZXE#ziOT^4e3U~cBM2n&puXn+Y(jCPzMdBHYxa-NKOk0x{wrhueJI$xl!nQ&f3$5# ztd0u$QiNEuf53ZOR7I|rgvKc;`OP#5PfCbymwhjBNzXQyY2xKwOI0TeIxqQFA5*o9&X z3dp!c&`PQ(4{(h5`v^V)P=(hkrOv+K7w`3N+FoxK(~Ts6Q=Y;YbdS8AQP&&H_36!y zST4(G4K+Xw3D@6?HrnR(dccEzikPBIaC>Us4$1froPw^5cJGGw*SS3$byM|FV~4c>PvS8 z$d6TYHrz#X+tBpVrTOw~$sashE=~IP3_?be%iPMKc)NeRYSzJZ{@1 zas^$9b&p&lm!B{B_3hPm`O4#S$L;5y!iU<}xjD%Zb6&f*QE^3dW25fMw+3D51-M`4 zqD0~7BA1$XzNpS6nr&yFs`&sIN-UIB`cQ|RE5K@|cf0k>3!8!$+S(olr%w+DU@cSV z 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 A82|tP literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/datastore/sortable_pb_encoder.py b/google_appengine/google/appengine/datastore/sortable_pb_encoder.py new file mode 100644 index 0000000..e1d4e65 --- /dev/null +++ b/google_appengine/google/appengine/datastore/sortable_pb_encoder.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""An Encoder class for Protocol Buffers that preserves sorting characteristics. + +This is used by datastore_sqlite_stub in order to index entities in a fashion +that preserves the datastore's sorting semantics. Broadly, there are four +changes from regular PB encoding: + + - Strings are escaped and null terminated instead of length-prefixed. The + escaping replaces \0 with \1\1 and \1 with \1\2, thus preserving the ordering + of the original string. + - Variable length integers are encoded using a variable length encoding that + preserves order. The first byte stores the absolute value if it's between + -119 to 119, otherwise it stores the number of bytes that follow. + - Numbers are stored big endian instead of little endian. + - Negative doubles are entirely negated, while positive doubles have their sign + bit flipped. + +Warning: + Due to the way nested Protocol Buffers are encoded, this encoder will NOT + preserve sorting characteristics for embedded protocol buffers! +""" + + + + + + + +import array +import struct + +from google.net.proto import ProtocolBuffer + + +_MAX_UNSIGNED_BYTE = 255 + +_MAX_LONG_BYTES = 8 + +_MAX_INLINE = (_MAX_UNSIGNED_BYTE - (2 * _MAX_LONG_BYTES)) / 2 +_MIN_INLINE = -_MAX_INLINE +_OFFSET = 1 + 8 +_POS_OFFSET = _OFFSET + _MAX_INLINE * 2 + + +class Encoder(ProtocolBuffer.Encoder): + """Encodes Protocol Buffers in a form that sorts nicely.""" + + def put16(self, value): + if value < 0 or value >= (1<<16): + raise ProtocolBuffer.ProtocolBufferEncodeError, 'u16 too big' + self.buf.append((value >> 8) & 0xff) + self.buf.append((value >> 0) & 0xff) + return + + def put32(self, value): + if value < 0 or value >= (1L<<32): + raise ProtocolBuffer.ProtocolBufferEncodeError, 'u32 too big' + self.buf.append((value >> 24) & 0xff) + self.buf.append((value >> 16) & 0xff) + self.buf.append((value >> 8) & 0xff) + self.buf.append((value >> 0) & 0xff) + return + + def put64(self, value): + if value < 0 or value >= (1L<<64): + raise ProtocolBuffer.ProtocolBufferEncodeError, 'u64 too big' + self.buf.append((value >> 56) & 0xff) + self.buf.append((value >> 48) & 0xff) + self.buf.append((value >> 40) & 0xff) + self.buf.append((value >> 32) & 0xff) + self.buf.append((value >> 24) & 0xff) + self.buf.append((value >> 16) & 0xff) + self.buf.append((value >> 8) & 0xff) + self.buf.append((value >> 0) & 0xff) + return + + def _PutVarInt(self, value): + if value is None: + self.buf.append(0) + return + + if value >= _MIN_INLINE and value <= _MAX_INLINE: + value = _OFFSET + (value - _MIN_INLINE) + self.buf.append(value & 0xff) + return + + negative = False + + if value < 0: + value = _MIN_INLINE - value + negative = True + else: + value = value - _MAX_INLINE + + len = 0 + w = value + while w > 0: + w >>= 8 + len += 1 + + if negative: + head = _OFFSET - len + else: + head = _POS_OFFSET + len + self.buf.append(head & 0xff) + + for i in range(len - 1, -1, -1): + b = value >> (i * 8) + if negative: + b = _MAX_UNSIGNED_BYTE - (b & 0xff) + self.buf.append(b & 0xff) + + def putVarInt32(self, value): + if value >= 0x80000000 or value < -0x80000000: + raise ProtocolBuffer.ProtocolBufferEncodeError, 'int32 too big' + self._PutVarInt(value) + + def putVarInt64(self, value): + if value >= 0x8000000000000000 or value < -0x8000000000000000: + raise ProtocolBuffer.ProtocolBufferEncodeError, 'int64 too big' + self._PutVarInt(value) + + def putVarUint64(self, value): + if value < 0 or value >= 0x10000000000000000: + raise ProtocolBuffer.ProtocolBufferEncodeError, 'uint64 too big' + self._PutVarInt(value) + + def putFloat(self, value): + encoded = array.array('B') + encoded.fromstring(struct.pack('>f', value)) + if value < 0: + encoded[0] ^= 0xFF + encoded[1] ^= 0xFF + encoded[2] ^= 0xFF + encoded[3] ^= 0xFF + else: + encoded[0] ^= 0x80 + self.buf.extend(encoded) + + def putDouble(self, value): + encoded = array.array('B') + encoded.fromstring(struct.pack('>d', value)) + if value < 0: + encoded[0] ^= 0xFF + encoded[1] ^= 0xFF + encoded[2] ^= 0xFF + encoded[3] ^= 0xFF + encoded[4] ^= 0xFF + encoded[5] ^= 0xFF + encoded[6] ^= 0xFF + encoded[7] ^= 0xFF + else: + encoded[0] ^= 0x80 + self.buf.extend(encoded) + + def putPrefixedString(self, value): + self.buf.fromstring(value.replace('\1', '\1\2').replace('\0', '\1\1') + '\0') + + +class Decoder(ProtocolBuffer.Decoder): + def __init__(self, buf, idx=0, limit=None): + if not limit: + limit = len(buf) + ProtocolBuffer.Decoder.__init__(self, buf, idx, limit) + + def get16(self): + if self.idx + 2 > self.limit: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'truncated' + c = self.buf[self.idx] + d = self.buf[self.idx + 1] + self.idx += 2 + return (c << 8) | d + + def get32(self): + if self.idx + 4 > self.limit: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'truncated' + c = long(self.buf[self.idx]) + d = self.buf[self.idx + 1] + e = self.buf[self.idx + 2] + f = self.buf[self.idx + 3] + self.idx += 4 + return (c << 24) | (d << 16) | (e << 8) | f + + def get64(self): + if self.idx + 8 > self.limit: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'truncated' + c = long(self.buf[self.idx]) + d = long(self.buf[self.idx + 1]) + e = long(self.buf[self.idx + 2]) + f = long(self.buf[self.idx + 3]) + g = long(self.buf[self.idx + 4]) + h = self.buf[self.idx + 5] + i = self.buf[self.idx + 6] + j = self.buf[self.idx + 7] + self.idx += 8 + return ((c << 56) | (d << 48) | (e << 40) | (f << 32) | (g << 24) + | (h << 16) | (i << 8) | j) + + def getVarInt64(self): + b = self.get8() + if b >= _OFFSET and b <= _POS_OFFSET: + return b - _OFFSET + _MIN_INLINE + if b == 0: + return None + + if b < _OFFSET: + negative = True + bytes = _OFFSET - b + else: + negative = False + bytes = b - _POS_OFFSET + + ret = 0 + for i in range(bytes): + b = self.get8() + if negative: + b = _MAX_UNSIGNED_BYTE - b + ret = ret << 8 | b + + if negative: + return _MIN_INLINE - ret + else: + return ret + _MAX_INLINE + + def getVarInt32(self): + result = self.getVarInt64() + if result >= 0x80000000L or result < -0x80000000L: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'corrupted' + return result + + def getVarUint64(self): + result = self.getVarInt64() + if result < 0: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'corrupted' + return result + + def getFloat(self): + if self.idx + 4 > self.limit: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'truncated' + a = self.buf[self.idx:self.idx+4] + self.idx += 4 + if a[0] & 0x80: + a[0] ^= 0x80 + else: + a = [x ^ 0xFF for x in a] + return struct.unpack('>f', array.array('B', a).tostring())[0] + + def getDouble(self): + if self.idx + 8 > self.limit: + raise ProtocolBuffer.ProtocolBufferDecodeError, 'truncated' + a = self.buf[self.idx:self.idx+8] + self.idx += 8 + if a[0] & 0x80: + a[0] ^= 0x80 + else: + a = [x ^ 0xFF for x in a] + return struct.unpack('>d', array.array('B', a).tostring())[0] + + def getPrefixedString(self): + end_idx = self.idx + while self.buf[end_idx] != 0: + end_idx += 1 + + data = array.array('B', self.buf[self.idx:end_idx]).tostring() + self.idx = end_idx + 1 + return data.replace('\1\1', '\0').replace('\1\2', '\1') diff --git a/google_appengine/google/appengine/datastore/sortable_pb_encoder.pyc b/google_appengine/google/appengine/datastore/sortable_pb_encoder.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da253f0337af2e87a3a51541465b5513955961a1 GIT binary patch literal 10713 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/dist/__init__.py b/google_appengine/google/appengine/dist/__init__.py new file mode 100755 index 0000000..237b487 --- /dev/null +++ b/google_appengine/google/appengine/dist/__init__.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Specify the modules for which a stub exists.""" + +__all__ = [ + + 'ftplib', + 'httplib', + 'neo_cgi', + 'py_imp', + 'select', + 'socket', + 'subprocess', + 'tempfile', + + 'use_library', + ] + +from google.appengine.dist import _library + +use_library = _library.use_library diff --git a/google_appengine/google/appengine/dist/__init__.pyc b/google_appengine/google/appengine/dist/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..883841cef994c25dd0dde62c758888a12ec77cd3 GIT binary patch literal 489 zcwSXdJx{|h5WOV*N=qe#1QSEX3@G^n2r+bEKn(4c#Y*i*#8Agj(C!?> z0(K#&2#N>-j6jZ{AoUe&(xrEMAlMk>vKkWCVA5)_S7?(;_sOA=yh+N$y8Sl6cWIr? z+=LEP&M3KcQ=*Og=N)RsYk8EIfyve!sYQj{ov7wJbZ+kHertNo(ONg7(qU^Vsn9K` z*jr>|+L!$xzbVqdE4=Kt{g#e>%iqeF(44U|FO?#GOT5;)R+yE>U{}iyGeI9%`!TM) zQwkSgUkLI>x0rXlFLl1shfe8I*!*O~{*2Cz{J-IybH-#R9b=gpj!%6&jq>TnyWbH^ NMp1CF!Z^APe*m5wd6xhH literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/dist/_library.py b/google_appengine/google/appengine/dist/_library.py new file mode 100755 index 0000000..67df388 --- /dev/null +++ b/google_appengine/google/appengine/dist/_library.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Code to exist off of google.appengine.dist. + +Kept in a separate file from the __init__ module for testing purposes. +""" + + +__all__ = ['use_library'] + + +try: + import distutils.version +except ImportError: + distutils = None + +import os +import sys + +server_software = os.getenv('SERVER_SOFTWARE') +USING_SDK = not server_software or server_software.startswith('Dev') +del server_software + +if not USING_SDK: + import google + this_version = os.path.dirname(os.path.dirname(google.__file__)) + versions = os.path.dirname(this_version) + PYTHON_LIB = os.path.dirname(versions) + del google, this_version, versions +else: + PYTHON_LIB = '/base/python_lib' + +installed = {} + + +def SetAllowedModule(_): + pass + + +class UnacceptableVersionError(Exception): + """Raised when a version of a package that is unacceptable is requested.""" + pass + + +def DjangoVersion(): + """Discover the version of Django installed. + + Returns: + A distutils.version.LooseVersion. + """ + import django + return distutils.version.LooseVersion('.'.join(map(str, django.VERSION))) + + +def PylonsVersion(): + """Discover the version of Pylons installed. + + Returns: + A distutils.version.LooseVersion. + """ + import pylons + return distutils.version.LooseVersion(pylons.__version__) + + +PACKAGES = { + 'django': (DjangoVersion, + {'0.96': None, + '1.0': None, + '1.1': None, + }), + + + + + + + + '_test': (lambda: distutils.version.LooseVersion('1.0'), {'1.0': None}), + '_testpkg': (lambda: distutils.version.LooseVersion('1.0'), + {'1.0': set([('_test', '1.0')])}), + } + + +def EqualVersions(version, baseline): + """Test that a version is acceptable as compared to the baseline. + + Meant to be used to compare version numbers as returned by a package itself + and not user input. + + Args: + version: distutils.version.LooseVersion. + The version that is being checked. + baseline: distutils.version.LooseVersion. + The version that one hopes version compares equal to. + + Returns: + A bool indicating whether the versions are considered equal. + """ + baseline_tuple = baseline.version + truncated_tuple = version.version[:len(baseline_tuple)] + if truncated_tuple == baseline_tuple: + return True + else: + return False + + +def AllowInstalledLibrary(name, desired): + """Allow the use of a package without performing a version check. + + Needed to clear a package's dependencies in case the dependencies need to be + imported in order to perform a version check. The version check is skipped on + the dependencies because the assumption is that the package that triggered + the call would not be installed without the proper dependencies (which might + be a different version than what the package explicitly requires). + + Args: + name: Name of package. + desired: Desired version. + + Raises: + UnacceptableVersion Error if the installed version of a package is + unacceptable. + """ + if name == 'django' and desired != '0.96': + tail = os.path.join('lib', 'django') + sys.path[:] = [dirname + for dirname in sys.path + if not dirname.endswith(tail)] + CallSetAllowedModule(name, desired) + dependencies = PACKAGES[name][1][desired] + if dependencies: + for dep_name, dep_version in dependencies: + AllowInstalledLibrary(dep_name, dep_version) + installed[name] = desired, False + + +def CheckInstalledLibrary(name, desired): + """Check that the library and its dependencies are installed. + + Args: + name: Name of the library that should be installed. + desired: The desired version. + + Raises: + UnacceptableVersionError if the installed version of a package is + unacceptable. + """ + dependencies = PACKAGES[name][1][desired] + if dependencies: + for dep_name, dep_version in dependencies: + AllowInstalledLibrary(dep_name, dep_version) + CheckInstalledVersion(name, desired, explicit=True) + + +def CheckInstalledVersion(name, desired, explicit): + """Check that the installed version of a package is acceptable. + + Args: + name: Name of package. + desired: Desired version string. + explicit: Explicitly requested by the user or implicitly because of a + dependency. + + Raises: + UnacceptableVersionError if the installed version of a package is + unacceptable. + """ + CallSetAllowedModule(name, desired) + find_version = PACKAGES[name][0] + installed_version = find_version() + desired_version = distutils.version.LooseVersion(desired) + if not EqualVersions(installed_version, desired_version): + raise UnacceptableVersionError( + '%s %s was requested, but %s is already in use' % + (name, desired_version, installed_version)) + installed[name] = desired, explicit + + +def CallSetAllowedModule(name, desired): + """Helper to call SetAllowedModule(name), after special-casing Django.""" + if name == 'django' and desired != '0.96': + tail = os.path.join('lib', 'django') + sys.path[:] = [dirname + for dirname in sys.path + if not dirname.endswith(tail)] + SetAllowedModule(name) + + +def CreatePath(name, version): + """Create the path to a package.""" + package_dir = '%s-%s' % (name, version) + return os.path.join(PYTHON_LIB, 'versions', 'third_party', package_dir) + + +def RemoveLibrary(name): + """Remove a library that has been installed.""" + installed_version, _ = installed[name] + path = CreatePath(name, installed_version) + try: + sys.path.remove(path) + except ValueError: + pass + del installed[name] + + +def AddLibrary(name, version, explicit): + """Add a library to sys.path and 'installed'.""" + sys.path.insert(1, CreatePath(name, version)) + installed[name] = version, explicit + + +def InstallLibrary(name, version, explicit=True): + """Install a package. + + If the installation is explicit then the user made the installation request, + not a package as a dependency. Explicit installation leads to stricter + version checking. + + Args: + name: Name of the requested package (already validated as available). + version: The desired version (already validated as available). + explicit: Explicitly requested by the user or implicitly because of a + dependency. + """ + installed_version, explicitly_installed = installed.get(name, [None] * 2) + if name in sys.modules: + if explicit: + CheckInstalledVersion(name, version, explicit=True) + return + elif installed_version: + if version == installed_version: + return + if explicit: + if explicitly_installed: + raise ValueError('%s %s requested, but %s already in use' % + (name, version, installed_version)) + RemoveLibrary(name) + else: + version_ob = distutils.version.LooseVersion(version) + installed_ob = distutils.version.LooseVersion(installed_version) + if version_ob <= installed_ob: + return + else: + RemoveLibrary(name) + AddLibrary(name, version, explicit) + dep_details = PACKAGES[name][1][version] + if not dep_details: + return + for dep_name, dep_version in dep_details: + InstallLibrary(dep_name, dep_version, explicit=False) + + +def use_library(name, version): + """Specify a third-party package to use. + + Args: + name: Name of package to use. + version: Version of the package to use (string). + """ + if name not in PACKAGES: + raise ValueError('%s is not a supported package' % name) + versions = PACKAGES[name][1].keys() + if version not in versions: + raise ValueError('%s is not a supported version for %s; ' + 'supported versions are %s' % (version, name, versions)) + if USING_SDK: + CheckInstalledLibrary(name, version) + else: + InstallLibrary(name, version, explicit=True) + + +if not USING_SDK: + InstallLibrary('django', '0.96', explicit=False) diff --git a/google_appengine/google/appengine/dist/_library.pyc b/google_appengine/google/appengine/dist/_library.pyc new file mode 100644 index 0000000000000000000000000000000000000000..743a259e5e5f94a2adfb87ae6e2c9b3181580299 GIT binary patch literal 9523 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/dist/ftplib.py b/google_appengine/google/appengine/dist/ftplib.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/dist/ftplib.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/dist/httplib.py b/google_appengine/google/appengine/dist/httplib.py new file mode 100755 index 0000000..c1bee3a --- /dev/null +++ b/google_appengine/google/appengine/dist/httplib.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Copyright 2008 Python Software Foundation, Ian Bicking, and Google.""" + +import mimetools +import StringIO +import sys + + +CONTINUE = 100 +SWITCHING_PROTOCOLS = 101 +PROCESSING = 102 +OK = 200 +CREATED = 201 +ACCEPTED = 202 +NON_AUTHORITATIVE_INFORMATION = 203 +NO_CONTENT = 204 +RESET_CONTENT = 205 +PARTIAL_CONTENT = 206 +MULTI_STATUS = 207 +IM_USED = 226 +MULTIPLE_CHOICES = 300 +MOVED_PERMANENTLY = 301 +FOUND = 302 +SEE_OTHER = 303 +NOT_MODIFIED = 304 +USE_PROXY = 305 +TEMPORARY_REDIRECT = 307 +BAD_REQUEST = 400 +UNAUTHORIZED = 401 +PAYMENT_REQUIRED = 402 +FORBIDDEN = 403 +NOT_FOUND = 404 +METHOD_NOT_ALLOWED = 405 +NOT_ACCEPTABLE = 406 +PROXY_AUTHENTICATION_REQUIRED = 407 +REQUEST_TIMEOUT = 408 +CONFLICT = 409 +GONE = 410 +LENGTH_REQUIRED = 411 +PRECONDITION_FAILED = 412 +REQUEST_ENTITY_TOO_LARGE = 413 +REQUEST_URI_TOO_LONG = 414 +UNSUPPORTED_MEDIA_TYPE = 415 +REQUESTED_RANGE_NOT_SATISFIABLE = 416 +EXPECTATION_FAILED = 417 +UNPROCESSABLE_ENTITY = 422 +LOCKED = 423 +FAILED_DEPENDENCY = 424 +UPGRADE_REQUIRED = 426 +INTERNAL_SERVER_ERROR = 500 +NOT_IMPLEMENTED = 501 +BAD_GATEWAY = 502 +SERVICE_UNAVAILABLE = 503 +GATEWAY_TIMEOUT = 504 +HTTP_VERSION_NOT_SUPPORTED = 505 +INSUFFICIENT_STORAGE = 507 +NOT_EXTENDED = 510 + +responses = { + 100: 'Continue', + 101: 'Switching Protocols', + + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Found', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: '(Unused)', + 307: 'Temporary Redirect', + + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Request Entity Too Large', + 414: 'Request-URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Requested Range Not Satisfiable', + 417: 'Expectation Failed', + + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', +} + +HTTP_PORT = 80 +HTTPS_PORT = 443 + + + + + +class HTTPConnection: + + + protocol = 'http' + default_port = HTTP_PORT + _allow_truncated = True + _follow_redirects = False + + def __init__(self, host, port=None, strict=False, timeout=None): + from google.appengine.api import urlfetch + self._fetch = urlfetch.fetch + self._method_map = { + 'GET': urlfetch.GET, + 'POST': urlfetch.POST, + 'HEAD': urlfetch.HEAD, + 'PUT': urlfetch.PUT, + 'DELETE': urlfetch.DELETE, + } + self.host = host + self.port = port + self._method = self._url = None + self._body = '' + self.headers = [] + + def connect(self): + pass + + def request(self, method, url, body=None, headers=None): + self._method = method + self._url = url + try: + self._body = body.read() + except AttributeError: + self._body = body + if headers is None: + headers = [] + elif hasattr(headers, 'items'): + headers = headers.items() + self.headers = headers + + def putrequest(self, request, selector, skip_host=False, skip_accept_encoding=False): + self._method = request + self._url = selector + + def putheader(self, header, *lines): + line = '\r\n\t'.join([str(line) for line in lines]) + self.headers.append((header, line)) + + def endheaders(self): + pass + + def set_debuglevel(self, level=None): + pass + + def send(self, data): + self._body += data + + def getresponse(self): + if self.port and self.port != self.default_port: + host = '%s:%s' % (self.host, self.port) + else: + host = self.host + if not self._url.startswith(self.protocol): + url = '%s://%s%s' % (self.protocol, host, self._url) + else: + url = self._url + headers = dict(self.headers) + + try: + method = self._method_map[self._method.upper()] + except KeyError: + raise ValueError("%r is an unrecognized HTTP method" % self._method) + + response = self._fetch(url, self._body, method, headers, + self._allow_truncated, self._follow_redirects) + return HTTPResponse(response) + + def close(self): + pass + + +class HTTPSConnection(HTTPConnection): + + protocol = 'https' + default_port = HTTPS_PORT + + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=False, timeout=None): + if key_file is not None or cert_file is not None: + raise NotImplementedError( + "key_file and cert_file arguments are not implemented") + HTTPConnection.__init__(self, host, port=port, strict=strict, + timeout=timeout) + + +class HTTPResponse(object): + + def __init__(self, fetch_response): + self._fetch_response = fetch_response + self.fp = StringIO.StringIO(fetch_response.content) + + def __getattr__(self, attr): + return getattr(self.fp, attr) + + def getheader(self, name, default=None): + return self._fetch_response.headers.get(name, default) + + def getheaders(self): + return self._fetch_response.headers.items() + + @property + def msg(self): + msg = mimetools.Message(StringIO.StringIO('')) + for name, value in self._fetch_response.headers.items(): + msg[name] = str(value) + return msg + + version = 11 + + @property + def status(self): + return self._fetch_response.status_code + + @property + def reason(self): + return responses.get(self._fetch_response.status_code, 'Unknown') + + +class HTTP: + "Compatibility class with httplib.py from 1.5." + + _http_vsn = 11 + _http_vsn_str = 'HTTP/1.1' + + debuglevel = 0 + + _connection_class = HTTPConnection + + def __init__(self, host='', port=None, strict=None): + "Provide a default host, since the superclass requires one." + + if port == 0: + port = None + + self._setup(self._connection_class(host, port, strict)) + + def _setup(self, conn): + self._conn = conn + + self.send = conn.send + self.putrequest = conn.putrequest + self.endheaders = conn.endheaders + self.set_debuglevel = conn.set_debuglevel + + conn._http_vsn = self._http_vsn + conn._http_vsn_str = self._http_vsn_str + + self.file = None + + def connect(self, host=None, port=None): + "Accept arguments to set the host/port, since the superclass doesn't." + self.__init__(host, port) + + def getfile(self): + "Provide a getfile, since the superclass' does not use this concept." + return self.file + + def putheader(self, header, *values): + "The superclass allows only one value argument." + self._conn.putheader(header, '\r\n\t'.join([str(v) for v in values])) + + def getreply(self): + """Compat definition since superclass does not define it. + + Returns a tuple consisting of: + - server status code (e.g. '200' if all goes well) + - server "reason" corresponding to status code + - any RFC822 headers in the response from the server + """ + response = self._conn.getresponse() + + self.headers = response.msg + self.file = response.fp + return response.status, response.reason, response.msg + + def close(self): + self._conn.close() + + self.file = None + + +class HTTPS(HTTP): + """Compatibility with 1.5 httplib interface + + Python 1.5.2 did not have an HTTPS class, but it defined an + interface for sending http requests that is also useful for + https. + """ + + _connection_class = HTTPSConnection + + def __init__(self, host='', port=None, key_file=None, cert_file=None, + strict=None): + if key_file is not None or cert_file is not None: + raise NotImplementedError( + "key_file and cert_file arguments are not implemented") + + + if port == 0: + port = None + self._setup(self._connection_class(host, port, key_file, + cert_file, strict)) + + self.key_file = key_file + self.cert_file = cert_file + + +class HTTPException(Exception): + pass + +class NotConnected(HTTPException): + pass + +class InvalidURL(HTTPException): + pass + +class UnknownProtocol(HTTPException): + def __init__(self, version): + self.version = version + HTTPException.__init__(self, version) + +class UnknownTransferEncoding(HTTPException): + pass + +class UnimplementedFileMode(HTTPException): + pass + +class IncompleteRead(HTTPException): + def __init__(self, partial): + self.partial = partial + HTTPException.__init__(self, partial) + +class ImproperConnectionState(HTTPException): + pass + +class CannotSendRequest(ImproperConnectionState): + pass + +class CannotSendHeader(ImproperConnectionState): + pass + +class ResponseNotReady(ImproperConnectionState): + pass + +class BadStatusLine(HTTPException): + def __init__(self, line): + self.line = line + HTTPException.__init__(self, line) + +error = HTTPException diff --git a/google_appengine/google/appengine/dist/neo_cgi.py b/google_appengine/google/appengine/dist/neo_cgi.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/dist/neo_cgi.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/dist/py_imp.py b/google_appengine/google/appengine/dist/py_imp.py new file mode 100755 index 0000000..cb097bc --- /dev/null +++ b/google_appengine/google/appengine/dist/py_imp.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Stub replacement for Python's imp module.""" + + +import os +import sys + + +PY_SOURCE, PY_COMPILED, C_EXTENSION = 1, 2, 3 +PKG_DIRECTORY, C_BUILTIN, PY_FROZEN = 5, 6, 7 + + +def get_magic(): + """Return the magic string used to recognize byte-compiled code files.""" + return '\xb3\xf2\r\n' + + +_PY_SOURCE_SUFFIX = ('.py', 'U', PY_SOURCE) +_PKG_DIRECTORY_SUFFIX = ('', '', PKG_DIRECTORY) + + +def get_suffixes(): + """Return a list that describes the files that find_module() looks for.""" + return [_PY_SOURCE_SUFFIX] + + +def find_module(name, path=None): + """Try to find the named module on the given search path or sys.path.""" + if path == None: + path = sys.path + + for directory in path: + filename = os.path.join(directory, '%s.py' % name) + if os.path.exists(filename): + return open(filename, 'U'), filename, _PY_SOURCE_SUFFIX + + dirname = os.path.join(directory, name) + filename = os.path.join(dirname, '__init__.py') + if os.path.exists(filename): + return None, dirname, _PKG_DIRECTORY_SUFFIX + + raise ImportError('No module named %s' % name) + + +def load_module(name, file_, pathname, description): + """Load or reload the specified module. + + Please note that this function has only rudimentary supported on App Engine: + Only loading packages is supported. + """ + suffix, mode, type_ = description + if type_ == PKG_DIRECTORY: + if name in sys.modules: + mod = sys.modules[name] + else: + mod = new_module(name) + sys.modules[name] = mod + filename = os.path.join(pathname, '__init__.py') + mod.__file__ = filename + execfile(filename, mod.__dict__, mod.__dict__) + return mod + else: + raise NotImplementedError('Only importing packages is supported on ' + 'App Engine') + + +def new_module(name): + """Return a new empty module object.""" + return type(sys)(name) + + +def lock_held(): + """Return False since threading is not supported.""" + return False + + +def acquire_lock(): + """Acquiring the lock is a no-op since no threading is supported.""" + pass + + +def release_lock(): + """There is no lock to release since acquiring is a no-op when there is no + threading.""" + pass + + +def init_builtin(name): + raise NotImplementedError('This function is not supported on App Engine.') + + +def init_frozen(name): + raise NotImplementedError('This function is not supported on App Engine.') + + +def is_builtin(name): + return name in sys.builtin_module_names + + +def is_frozen(name): + return False + + +def load_compiled(name, pathname, file_=None): + raise NotImplementedError('This function is not supported on App Engine.') + + +def load_dynamic(name, pathname, file_=None): + raise NotImplementedError('This function is not supported on App Engine.') + + +def load_source(name, pathname, file_=None): + raise NotImplementedError('This function is not supported on App Engine.') + + +class NullImporter(object): + """Null importer object""" + + def __init__(self, path_string): + if not path_string: + raise ImportError("empty pathname") + elif os.path.isdir(path_string): + raise ImportError("existing directory") + + def find_module(self, fullname): + return None diff --git a/google_appengine/google/appengine/dist/py_zipimport.py b/google_appengine/google/appengine/dist/py_zipimport.py new file mode 100755 index 0000000..1ec76b5 --- /dev/null +++ b/google_appengine/google/appengine/dist/py_zipimport.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Pure Python zipfile importer. + +This approximates the standard zipimport module, which isn't supported +by Google App Engine. See PEP 302 for more information about the API +for import hooks. + +Usage: + import py_zipimport + +As a side effect of importing, the module overrides sys.path_hooks, +and also creates an alias 'zipimport' for itself. When your app is +running in Google App Engine production, you don't even need to import +it, since this is already done for you. In the Google App Engine SDK +this module is not used; instead, the standard zipimport module is +used. +""" + + +__all__ = ['ZipImportError', 'zipimporter'] + + +import os +import sys +import types +import UserDict +import zipfile + + +_SEARCH_ORDER = [ + + ('.py', False), + ('/__init__.py', True), +] + + +_zipfile_cache = {} + + +class ZipImportError(ImportError): + """Exception raised by zipimporter objects.""" + + +class zipimporter: + """A PEP-302-style importer that can import from a zipfile. + + Just insert or append this class (not an instance) to sys.path_hooks + and you're in business. Instances satisfy both the 'importer' and + 'loader' APIs specified in PEP 302. + """ + + def __init__(self, path_entry): + """Constructor. + + Args: + path_entry: The entry in sys.path. This should be the name of an + existing zipfile possibly with a path separator and a prefix + path within the archive appended, e.g. /x/django.zip or + /x/django.zip/foo/bar. + + Raises: + ZipImportError if the path_entry does not represent a valid + zipfile with optional prefix. + """ + archive = path_entry + prefix = '' + while not os.path.lexists(archive): + head, tail = os.path.split(archive) + if head == archive: + msg = 'Nothing found for %r' % path_entry + raise ZipImportError(msg) + archive = head + prefix = os.path.join(tail, prefix) + if not os.path.isfile(archive): + msg = 'Non-file %r found for %r' % (archive, path_entry) + raise ZipImportError(msg) + self.archive = archive + self.prefix = os.path.join(prefix, '') + self.zipfile = _zipfile_cache.get(archive) + if self.zipfile is None: + try: + self.zipfile = zipfile.ZipFile(self.archive) + except (EnvironmentError, zipfile.BadZipfile), err: + msg = 'Can\'t open zipfile %s: %s: %s' % (self.archive, + err.__class__.__name__, err) + import logging + logging.warn(msg) + raise ZipImportError(msg) + else: + _zipfile_cache[archive] = self.zipfile + import logging + logging.info('zipimporter(%r, %r)', archive, prefix) + + def __repr__(self): + """Return a string representation matching zipimport.c.""" + name = self.archive + if self.prefix: + name = os.path.join(name, self.prefix) + return '' % name + + def _get_info(self, fullmodname): + """Internal helper for find_module() and load_module(). + + Args: + fullmodname: The dot-separated full module name, e.g. 'django.core.mail'. + + Returns: + A tuple (submodname, is_package, relpath) where: + submodname: The final component of the module name, e.g. 'mail'. + is_package: A bool indicating whether this is a package. + relpath: The path to the module's source code within to the zipfile. + + Raises: + ImportError if the module is not found in the archive. + """ + parts = fullmodname.split('.') + submodname = parts[-1] + for suffix, is_package in _SEARCH_ORDER: + relpath = os.path.join(self.prefix, + submodname + suffix.replace('/', os.sep)) + try: + self.zipfile.getinfo(relpath.replace(os.sep, '/')) + except KeyError: + pass + else: + return submodname, is_package, relpath + msg = ('Can\'t find module %s in zipfile %s with prefix %r' % + (fullmodname, self.archive, self.prefix)) + raise ZipImportError(msg) + + def _get_source(self, fullmodname): + """Internal helper for load_module(). + + Args: + fullmodname: The dot-separated full module name, e.g. 'django.core.mail'. + + Returns: + A tuple (submodname, is_package, fullpath, source) where: + submodname: The final component of the module name, e.g. 'mail'. + is_package: A bool indicating whether this is a package. + fullpath: The path to the module's source code including the + zipfile's filename. + source: The module's source code. + + Raises: + ImportError if the module is not found in the archive. + """ + submodname, is_package, relpath = self._get_info(fullmodname) + fullpath = '%s%s%s' % (self.archive, os.sep, relpath) + source = self.zipfile.read(relpath.replace(os.sep, '/')) + source = source.replace('\r\n', '\n') + source = source.replace('\r', '\n') + return submodname, is_package, fullpath, source + + def find_module(self, fullmodname, path=None): + """PEP-302-compliant find_module() method. + + Args: + fullmodname: The dot-separated full module name, e.g. 'django.core.mail'. + path: Optional and ignored; present for API compatibility only. + + Returns: + None if the module isn't found in the archive; self if it is found. + """ + try: + submodname, is_package, relpath = self._get_info(fullmodname) + except ImportError: + return None + else: + return self + + def load_module(self, fullmodname): + """PEP-302-compliant load_module() method. + + Args: + fullmodname: The dot-separated full module name, e.g. 'django.core.mail'. + + Returns: + The module object constructed from the source code. + + Raises: + SyntaxError if the module's source code is syntactically incorrect. + ImportError if there was a problem accessing the source code. + Whatever else can be raised by executing the module's source code. + """ + submodname, is_package, fullpath, source = self._get_source(fullmodname) + code = compile(source, fullpath, 'exec') + mod = sys.modules.get(fullmodname) + try: + if mod is None: + mod = sys.modules[fullmodname] = types.ModuleType(fullmodname) + mod.__loader__ = self + mod.__file__ = fullpath + mod.__name__ = fullmodname + if is_package: + mod.__path__ = [os.path.dirname(mod.__file__)] + exec code in mod.__dict__ + except: + if fullmodname in sys.modules: + del sys.modules[fullmodname] + raise + return mod + + + def get_data(self, fullpath): + """Return (binary) content of a data file in the zipfile.""" + prefix = os.path.join(self.archive, '') + if fullpath.startswith(prefix): + relpath = fullpath[len(prefix):] + elif os.path.isabs(fullpath): + raise IOError('Absolute path %r doesn\'t start with zipfile name %r' % + (fullpath, prefix)) + else: + relpath = fullpath + try: + return self.zipfile.read(relpath) + except KeyError: + raise IOError('Path %r not found in zipfile %r' % + (relpath, self.archive)) + + def is_package(self, fullmodname): + """Return whether a module is a package.""" + submodname, is_package, relpath = self._get_info(fullmodname) + return is_package + + def get_code(self, fullmodname): + """Return bytecode for a module.""" + submodname, is_package, fullpath, source = self._get_source(fullmodname) + return compile(source, fullpath, 'exec') + + def get_source(self, fullmodname): + """Return source code for a module.""" + submodname, is_package, fullpath, source = self._get_source(fullmodname) + return source + + +class ZipFileCache(UserDict.DictMixin): + """Helper class to export archive data in _zip_directory_cache. + + Just take the info from _zipfile_cache and convert it as required. + """ + + def __init__(self, archive): + _zipfile_cache[archive] + + self._archive = archive + + def keys(self): + return _zipfile_cache[self._archive].namelist() + + def __getitem__(self, filename): + info = _zipfile_cache[self._archive].getinfo(filename) + dt = info.date_time + dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) + dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] + return (os.path.join(self._archive, info.filename), info.compress_type, + info.compress_size, info.file_size, info.header_offset, dostime, + dosdate, info.CRC) + + +class ZipDirectoryCache(UserDict.DictMixin): + """Helper class to export _zip_directory_cache.""" + + def keys(self): + return _zipfile_cache.keys() + + def __getitem__(self, archive): + return ZipFileCache(archive) + + +_zip_directory_cache = ZipDirectoryCache() + + +sys.modules['zipimport'] = sys.modules[__name__] +sys.path_hooks[:] = [zipimporter] diff --git a/google_appengine/google/appengine/dist/py_zipimport.pyc b/google_appengine/google/appengine/dist/py_zipimport.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34ace73281319d0695526eb7389df97fd58d14a9 GIT binary patch literal 11000 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 JYF1ah{{?yTjOG9U literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/dist/select.py b/google_appengine/google/appengine/dist/select.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/dist/select.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/dist/socket.py b/google_appengine/google/appengine/dist/socket.py new file mode 100755 index 0000000..d9f731b --- /dev/null +++ b/google_appengine/google/appengine/dist/socket.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +AF_INET = None +SOCK_STREAM = None +SOCK_DGRAM = None + +_GLOBAL_DEFAULT_TIMEOUT = object() + + +class error(OSError): + pass + +class herror(error): + pass + +class gaierror(error): + pass + +class timeout(error): + pass + + +def _fileobject(fp, mode='rb', bufsize=-1, close=False): + """Assuming that the argument is a StringIO or file instance.""" + if not hasattr(fp, 'fileno'): + fp.fileno = lambda: None + return fp + +ssl = None diff --git a/google_appengine/google/appengine/dist/subprocess.py b/google_appengine/google/appengine/dist/subprocess.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/dist/subprocess.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/dist/tempfile.py b/google_appengine/google/appengine/dist/tempfile.py new file mode 100755 index 0000000..5c3999a --- /dev/null +++ b/google_appengine/google/appengine/dist/tempfile.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Temporary files. + +This module is a replacement for the stock tempfile module in Python, +and provides only in-memory temporary files as implemented by +cStringIO. The only functionality provided is the TemporaryFile +function. +""" + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +__all__ = [ + "TemporaryFile", + + "NamedTemporaryFile", "mkstemp", "mkdtemp", "mktemp", + "TMP_MAX", "gettempprefix", "tempdir", "gettempdir", +] + +TMP_MAX = 10000 + +template = "tmp" + +tempdir = None + +def TemporaryFile(mode='w+b', bufsize=-1, suffix="", + prefix=template, dir=None): + """Create and return a temporary file. + Arguments: + 'prefix', 'suffix', 'dir', 'mode', 'bufsize' are all ignored. + + Returns an object with a file-like interface. The file is in memory + only, and does not exist on disk. + """ + + return StringIO() + +def PlaceHolder(*args, **kwargs): + raise NotImplementedError("Only tempfile.TemporaryFile is available for use") + +NamedTemporaryFile = PlaceHolder +mkstemp = PlaceHolder +mkdtemp = PlaceHolder +mktemp = PlaceHolder +gettempprefix = PlaceHolder +tempdir = PlaceHolder +gettempdir = PlaceHolder diff --git a/google_appengine/google/appengine/ext/__init__.py b/google_appengine/google/appengine/ext/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/ext/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/ext/__init__.pyc b/google_appengine/google/appengine/ext/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecda50f601c8fffeabbe5e483330a5ce38aeeb15 GIT binary patch literal 158 zcwW2siI?l9Oo~r30~9a%knvjB+{28Lh_kcgiKNDhrCb_Wvr8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg|OViGF;1W?p7Ve7s&kWeEq+ESuc? Ol+v73JCKdVAR7QRmnKmF literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/admin/__init__.py b/google_appengine/google/appengine/ext/admin/__init__.py new file mode 100755 index 0000000..397e18b --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/__init__.py @@ -0,0 +1,1411 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Simple datastore view and interactive console, for use in dev_appserver.""" + + + + + +import cgi +import csv +import cStringIO +import datetime +import logging +import math +import mimetypes +import os +import os.path +import pickle +import pprint +import random +import sys +import time +import traceback +import types +import urllib +import urlparse +import wsgiref.handlers + +try: + from google.appengine.cron import groctimespecification + from google.appengine.api import croninfo +except ImportError: + HAVE_CRON = False +else: + HAVE_CRON = True + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import datastore +from google.appengine.api import datastore_admin +from google.appengine.api import datastore_types +from google.appengine.api import datastore_errors +from google.appengine.api import memcache +from google.appengine.api.labs import taskqueue +from google.appengine.api import users +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template + +_DEBUG = True + + +def ustr(value): + """Like str(), but UTF-8-encodes Unicode instead of failing.""" + try: + return str(value) + except UnicodeError: + return unicode(value).encode('UTF-8') + + +class ImageHandler(webapp.RequestHandler): + """Serves a static image. + + This exists because we don't want to burden the user with specifying + a static file handler for the image resources used by the admin tool. + """ + + PATH = '/images/.*' + + def get(self): + image_name = os.path.basename(self.request.path) + content_type, encoding = mimetypes.guess_type(image_name) + if not content_type or not content_type.startswith('image/'): + logging.debug('image_name=%r, content_type=%r, encoding=%r', + image_name, content_type, encoding) + self.error(404) + return + directory = os.path.dirname(__file__) + path = os.path.join(directory, 'templates', 'images', image_name) + try: + image_stream = open(path, 'rb') + except IOError, e: + logging.error('Cannot open image %s: %s', image_name, e) + self.error(404) + return + try: + image_data = image_stream.read() + finally: + image_stream.close() + self.response.headers['Content-Type'] = content_type + self.response.out.write(image_data) + + +class BaseRequestHandler(webapp.RequestHandler): + """Supplies a common template generation function. + + When you call generate(), we augment the template variables supplied with + the current user in the 'user' variable and the current webapp request + in the 'request' variable. + """ + + def generate(self, template_name, template_values={}): + base_path = self.base_path() + values = { + 'application_name': self.request.environ['APPLICATION_ID'], + 'user': users.get_current_user(), + 'request': self.request, + 'home_path': base_path + DefaultPageHandler.PATH, + 'datastore_path': base_path + DatastoreQueryHandler.PATH, + 'datastore_edit_path': base_path + DatastoreEditHandler.PATH, + 'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH, + 'interactive_path': base_path + InteractivePageHandler.PATH, + 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH, + 'memcache_path': base_path + MemcachePageHandler.PATH, + 'queues_path': base_path + QueuesPageHandler.PATH, + 'xmpp_path': base_path + XMPPPageHandler.PATH, + 'inboundmail_path': base_path + InboundMailPageHandler.PATH, + } + if HAVE_CRON: + values['cron_path'] = base_path + CronPageHandler.PATH + + values.update(template_values) + directory = os.path.dirname(__file__) + path = os.path.join(directory, os.path.join('templates', template_name)) + self.response.out.write(template.render(path, values, debug=_DEBUG)) + + def base_path(self): + """Returns the base path of this admin app, which is chosen by the user. + + The user specifies which paths map to this application in their app.cfg. + You can get that base path with this method. Combine with the constant + paths specified by the classes to construct URLs. + """ + path = self.__class__.PATH + return self.request.path[:-len(path)] + + def filter_url(self, args): + """Filters the current URL to only have the given list of arguments. + + For example, if your URL is /search?q=foo&num=100&start=10, then + + self.filter_url(['start', 'num']) => /search?num=100&start=10 + self.filter_url(['q']) => /search?q=10 + self.filter_url(['random']) => /search? + + """ + queries = [] + for arg in args: + value = self.request.get(arg) + if value: + queries.append(arg + '=' + urllib.quote_plus(self.request.get(arg))) + return self.request.path + '?' + '&'.join(queries) + + def in_production(self): + """Detects if app is running in production. + + Returns a boolean. + """ + server_software = os.environ['SERVER_SOFTWARE'] + return not server_software.startswith('Development') + + +class DefaultPageHandler(BaseRequestHandler): + """Redirects to the Datastore application by default.""" + + PATH = '/' + + def get(self): + if self.request.path.endswith('/'): + base = self.request.path[:-1] + else: + base = self.request.path + self.redirect(base + DatastoreQueryHandler.PATH) + + +class InteractivePageHandler(BaseRequestHandler): + """Shows our interactive console HTML.""" + PATH = '/interactive' + + def get(self): + self.generate('interactive.html') + + +class InteractiveExecuteHandler(BaseRequestHandler): + """Executes the Python code submitted in a POST within this context. + + For obvious reasons, this should only be available to administrators + of the applications. + """ + + PATH = InteractivePageHandler.PATH + '/execute' + + def post(self): + save_stdout = sys.stdout + results_io = cStringIO.StringIO() + try: + sys.stdout = results_io + + code = self.request.get('code') + code = code.replace("\r\n", "\n") + + try: + compiled_code = compile(code, '', 'exec') + exec(compiled_code, globals()) + except Exception, e: + traceback.print_exc(file=results_io) + finally: + sys.stdout = save_stdout + + results = results_io.getvalue() + self.generate('interactive-output.html', {'output': results}) + + +class CronPageHandler(BaseRequestHandler): + """Shows information about configured cron jobs in this application.""" + PATH = '/cron' + + def get(self, now=None): + """Shows template displaying the configured cron jobs.""" + if not now: + now = datetime.datetime.now() + values = {'request': self.request} + cron_info = _ParseCronYaml() + values['cronjobs'] = [] + values['now'] = str(now) + if cron_info and cron_info.cron: + for entry in cron_info.cron: + job = {} + values['cronjobs'].append(job) + if entry.description: + job['description'] = entry.description + else: + job['description'] = '(no description)' + if entry.timezone: + job['timezone'] = entry.timezone + job['url'] = entry.url + job['schedule'] = entry.schedule + schedule = groctimespecification.GrocTimeSpecification(entry.schedule) + matches = schedule.GetMatches(now, 3) + job['times'] = [] + for match in matches: + job['times'].append({'runtime': match.strftime("%Y-%m-%d %H:%M:%SZ"), + 'difference': str(match - now)}) + self.generate('cron.html', values) + + +class XMPPPageHandler(BaseRequestHandler): + """Tests XMPP requests.""" + PATH = '/xmpp' + + def get(self): + """Shows template displaying the XMPP.""" + xmpp_configured = True + values = { + 'xmpp_configured': xmpp_configured, + 'request': self.request + } + self.generate('xmpp.html', values) + + +class InboundMailPageHandler(BaseRequestHandler): + """Tests Mail requests.""" + PATH = '/inboundmail' + + def get(self): + """Shows template displaying the Inbound Mail form.""" + inboundmail_configured = True + values = { + 'inboundmail_configured': inboundmail_configured, + 'request': self.request + } + self.generate('inboundmail.html', values) + + +class QueuesPageHandler(BaseRequestHandler): + """Shows information about configured (and default) task queues.""" + PATH = '/queues' + + def __init__(self): + self.stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue') + + def get(self): + """Shows template displaying the configured task queues.""" + values = { + 'request': self.request, + 'queues': self.stub.GetQueues(), + } + self.generate('queues.html', values) + + def post(self): + """Handle modifying actions and/or redirect to GET page.""" + + if self.request.get('action:flushqueue'): + self.stub.FlushQueue(self.request.get('queue')) + self.redirect(self.request.path_url) + + +class TasksPageHandler(BaseRequestHandler): + """Shows information about a queue's tasks.""" + + PATH = '/tasks' + + PAGE_SIZE = 20 + + def __init__(self): + self.stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue') + + def get(self): + """Shows template displaying the queue's tasks.""" + queue = self.request.get('queue') + start = int(self.request.get('start', 0)) + all_tasks = self.stub.GetTasks(queue) + + next_start = start + self.PAGE_SIZE + tasks = all_tasks[start:next_start] + current_page = int(start / self.PAGE_SIZE) + 1 + pages = [] + for number in xrange(int(math.ceil(len(all_tasks) / + float(self.PAGE_SIZE)))): + pages.append({ + 'number': number + 1, + 'start': number * self.PAGE_SIZE + }) + if not all_tasks[next_start:]: + next_start = -1 + prev_start = start - self.PAGE_SIZE + if prev_start < 0: + prev_start = -1 + + values = { + 'request': self.request, + 'queue_name': queue, + 'tasks': tasks, + 'start_base_url': self.filter_url(['queue']), + 'prev_start': prev_start, + 'next_start': next_start, + 'pages': pages, + 'current_page': current_page, + } + self.generate('tasks.html', values) + + def post(self): + if self.request.get('action:deletetask'): + self.stub.DeleteTask(self.request.get('queue'), self.request.get('task')) + self.redirect(self.request.path_url + '?queue=' + self.request.get('queue')) + return + + +class MemcachePageHandler(BaseRequestHandler): + """Shows stats about memcache and query form to get values.""" + PATH = '/memcache' + + TYPES = ((str, str, 'String'), + (unicode, unicode, 'Unicode String'), + (bool, lambda value: MemcachePageHandler._ToBool(value), 'Boolean'), + (int, int, 'Integer'), + (long, long, 'Long Integer'), + (float, float, 'Float')) + DEFAULT_TYPESTR_FOR_NEW = 'String' + + @staticmethod + def _ToBool(string_value): + """Convert string to boolean value. + + Args: + string_value: A string. + + Returns: + Boolean. True if string_value is "true", False if string_value is + "false". This is case-insensitive. + + Raises: + ValueError: string_value not "true" or "false". + """ + string_value_low = string_value.lower() + if string_value_low not in ('false', 'true'): + raise ValueError('invalid literal for boolean: %s' % string_value) + return string_value_low == 'true' + + def _GetValueAndType(self, key): + """Fetch value from memcache and detect its type. + + Args: + key: String + + Returns: + (value, type), value is a Python object or None if the key was not set in + the cache, type is a string describing the type of the value. + """ + try: + value = memcache.get(key) + except (pickle.UnpicklingError, AttributeError, EOFError, ImportError, + IndexError), e: + msg = 'Failed to retrieve value from cache: %s' % e + return msg, 'error' + + if value is None: + return None, self.DEFAULT_TYPESTR_FOR_NEW + + for typeobj, _, typestr in self.TYPES: + if isinstance(value, typeobj): + break + else: + typestr = 'pickled' + value = pprint.pformat(value, indent=2) + + return value, typestr + + def _SetValue(self, key, type_, value): + """Convert a string value and store the result in memcache. + + Args: + key: String + type_: String, describing what type the value should have in the cache. + value: String, will be converted according to type_. + + Returns: + Result of memcache.set(ket, converted_value). True if value was set. + + Raises: + ValueError: Value can't be converted according to type_. + """ + for _, converter, typestr in self.TYPES: + if typestr == type_: + value = converter(value) + break + else: + raise ValueError('Type %s not supported.' % type_) + return memcache.set(key, value) + + def get(self): + """Show template and prepare stats and/or key+value to display/edit.""" + values = {'request': self.request, + 'message': self.request.get('message')} + + edit = self.request.get('edit') + key = self.request.get('key') + if edit: + key = edit + values['show_stats'] = False + values['show_value'] = False + values['show_valueform'] = True + values['types'] = [typestr for _, _, typestr in self.TYPES] + elif key: + values['show_stats'] = True + values['show_value'] = True + values['show_valueform'] = False + else: + values['show_stats'] = True + values['show_valueform'] = False + values['show_value'] = False + + if key: + values['key'] = key + values['value'], values['type'] = self._GetValueAndType(key) + values['key_exists'] = values['value'] is not None + + if values['type'] in ('pickled', 'error'): + values['writable'] = False + else: + values['writable'] = True + + if values['show_stats']: + memcache_stats = memcache.get_stats() + if not memcache_stats: + memcache_stats = {'hits': 0, 'misses': 0, 'byte_hits': 0, 'items': 0, + 'bytes': 0, 'oldest_item_age': 0} + values['stats'] = memcache_stats + try: + hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits'] + + memcache_stats['misses']) + except ZeroDivisionError: + hitratio = 0 + values['hitratio'] = hitratio + delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age']) + values['oldest_item_age'] = datetime.datetime.now() - delta_t + + self.generate('memcache.html', values) + + def _urlencode(self, query): + """Encode a dictionary into a URL query string. + + In contrast to urllib this encodes unicode characters as UTF8. + + Args: + query: Dictionary of key/value pairs. + + Returns: + String. + """ + return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')), + urllib.quote_plus(v.encode('utf8'))) + for k, v in query.iteritems()) + + def post(self): + """Handle modifying actions and/or redirect to GET page.""" + next_param = {} + + if self.request.get('action:flush'): + if memcache.flush_all(): + next_param['message'] = 'Cache flushed, all keys dropped.' + else: + next_param['message'] = 'Flushing the cache failed. Please try again.' + + elif self.request.get('action:display'): + next_param['key'] = self.request.get('key') + + elif self.request.get('action:edit'): + next_param['edit'] = self.request.get('key') + + elif self.request.get('action:delete'): + key = self.request.get('key') + result = memcache.delete(key) + if result == memcache.DELETE_NETWORK_FAILURE: + next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.' + % key) + elif result == memcache.DELETE_ITEM_MISSING: + next_param['message'] = 'Key "%s" not in cache.' % key + elif result == memcache.DELETE_SUCCESSFUL: + next_param['message'] = 'Key "%s" deleted.' % key + else: + next_param['message'] = ('Unknown return value. Key "%s" might still ' + 'exist.' % key) + + elif self.request.get('action:save'): + key = self.request.get('key') + value = self.request.get('value') + type_ = self.request.get('type') + next_param['key'] = key + try: + if self._SetValue(key, type_, value): + next_param['message'] = 'Key "%s" saved.' % key + else: + next_param['message'] = 'ERROR: Failed to save key "%s".' % key + except ValueError, e: + next_param['message'] = 'ERROR: Unable to encode value: %s' % e + + elif self.request.get('action:cancel'): + next_param['key'] = self.request.get('key') + + else: + next_param['message'] = 'Unknown action.' + + next = self.request.path_url + if next_param: + next = '%s?%s' % (next, self._urlencode(next_param)) + self.redirect(next) + + +class DatastoreRequestHandler(BaseRequestHandler): + """The base request handler for our datastore admin pages. + + We provide utility functions for quering the datastore and infering the + types of entity properties. + """ + + def start(self): + """Returns the santized "start" argument from the URL.""" + return self.request.get_range('start', min_value=0, default=0) + + def num(self): + """Returns the sanitized "num" argument from the URL.""" + return self.request.get_range('num', min_value=1, max_value=100, + default=10) + + def execute_query(self, start=0, num=0, no_order=False): + """Parses the URL arguments and executes the query. + + We return a tuple (list of entities, total entity count). + + If the appropriate URL arguments are not given, we return an empty + set of results and 0 for the entity count. + """ + kind = self.request.get('kind') + namespace = self.request.get('namespace') + if not namespace: + namespace = None + if not kind: + return ([], 0) + query = datastore.Query(kind, _namespace=namespace) + + order = self.request.get('order') + order_type = self.request.get('order_type') + if order and order_type: + order_type = DataType.get_by_name(order_type).python_type() + if order.startswith('-'): + direction = datastore.Query.DESCENDING + order = order[1:] + else: + direction = datastore.Query.ASCENDING + try: + query.Order((order, order_type, direction)) + except datastore_errors.BadArgumentError: + pass + + if not start: + start = self.start() + if not num: + num = self.num() + total = query.Count() + entities = query.Get(start + num)[start:] + return (entities, total) + + def get_key_values(self, entities): + """Returns the union of key names used by the given list of entities. + + We return the union as a dictionary mapping the key names to a sample + value from one of the entities for the key name. + """ + key_dict = {} + for entity in entities: + for key, value in entity.iteritems(): + if key_dict.has_key(key): + key_dict[key].append(value) + else: + key_dict[key] = [value] + return key_dict + + +class DatastoreQueryHandler(DatastoreRequestHandler): + """Our main request handler that executes queries and lists entities. + + We use execute_query() in our base request handler to parse URL arguments + and execute the datastore query. + """ + + PATH = '/datastore' + + def get_kinds(self, namespace): + """Get sorted list of kind names the datastore knows about. + + This should only be called in the development environment as GetSchema is + expensive and no caching is done. + + Args: + namespace: The namespace to fetch the schema for e.g. 'google.com'. It + is an error to pass in None. + + Returns: + A sorted list of kinds e.g. ['Book', 'Guest', Post']. + """ + assert namespace is not None + schema = datastore_admin.GetSchema(namespace=namespace) + kinds = [] + for entity_proto in schema: + kinds.append(entity_proto.key().path().element_list()[-1].type()) + kinds.sort() + return kinds + + def get(self): + """Formats the results from execute_query() for datastore.html. + + The only complex part of that process is calculating the pager variables + to generate the Gooooogle pager at the bottom of the page. + """ + result_set, total = self.execute_query() + key_values = self.get_key_values(result_set) + keys = key_values.keys() + keys.sort() + + headers = [] + for key in keys: + sample_value = key_values[key][0] + headers.append({ + 'name': key, + 'type': DataType.get(sample_value).name(), + }) + + entities = [] + edit_path = self.base_path() + DatastoreEditHandler.PATH + for entity in result_set: + attributes = [] + for key in keys: + if entity.has_key(key): + raw_value = entity[key] + data_type = DataType.get(raw_value) + value = data_type.format(raw_value) + short_value = data_type.short_format(raw_value) + additional_html = data_type.additional_short_value_html(raw_value) + else: + value = '' + short_value = '' + additional_html = '' + attributes.append({ + 'name': key, + 'value': value, + 'short_value': short_value, + 'additional_html': additional_html, + }) + entities.append({ + 'key': str(entity.key()), + 'key_name': entity.key().name(), + 'key_id': entity.key().id(), + 'shortened_key': str(entity.key())[:8] + '...', + 'attributes': attributes, + 'edit_uri': edit_path + '?key=' + str(entity.key()) + '&kind=' + urllib.quote(self.request.get('kind')) + '&next=' + urllib.quote(self.request.uri), + }) + + start = self.start() + num = self.num() + max_pager_links = 8 + current_page = start / num + num_pages = int(math.ceil(total * 1.0 / num)) + page_start = max(math.floor(current_page - max_pager_links / 2), 0) + page_end = min(page_start + max_pager_links, num_pages) + + pages = [] + for page in range(page_start + 1, page_end + 1): + pages.append({ + 'number': page, + 'start': (page - 1) * num, + }) + current_page += 1 + + in_production = self.in_production() + if in_production: + kinds = None + else: + kinds = self.get_kinds(self.request.get('namespace')) + + values = { + 'request': self.request, + 'in_production': in_production, + 'kinds': kinds, + 'kind': self.request.get('kind'), + 'order': self.request.get('order'), + 'headers': headers, + 'entities': entities, + 'message': self.request.get('msg'), + 'pages': pages, + 'current_page': current_page, + 'namespace': self.request.get('namespace'), + 'show_namespace': self.request.get('namespace', None) is not None, + 'num': num, + 'next_start': -1, + 'prev_start': -1, + 'start': start, + 'total': total, + 'start_base_url': self.filter_url(['kind', 'order', 'order_type', + 'namespace', 'num']), + 'order_base_url': self.filter_url(['kind', 'namespace', 'num']), + } + if current_page > 1: + values['prev_start'] = int((current_page - 2) * num) + if current_page < num_pages: + values['next_start'] = int(current_page * num) + + self.generate('datastore.html', values) + + +class DatastoreBatchEditHandler(DatastoreRequestHandler): + """Request handler for a batch operation on entities. + + Supports deleting multiple entities by key, then redirecting to another url. + """ + + PATH = DatastoreQueryHandler.PATH + '/batchedit' + + def post(self): + kind = self.request.get('kind') + + keys = [] + index = 0 + num_keys = int(self.request.get('numkeys')) + for i in xrange(1, num_keys+1): + key = self.request.get('key%d' % i) + if key: + keys.append(key) + + if self.request.get('action') == 'Delete': + num_deleted = 0 + for key in keys: + datastore.Delete(datastore.Key(key)) + num_deleted = num_deleted + 1 + message = '%d entit%s deleted.' % ( + num_deleted, ('ies', 'y')[num_deleted == 1]) + self.redirect( + '%s&msg=%s' % (self.request.get('next'), urllib.quote_plus(message))) + return + + self.error(404) + + +class DatastoreEditHandler(DatastoreRequestHandler): + """Request handler for the entity create/edit form. + + We determine how to generate a form to edit an entity by doing a query + on the entity kind and looking at the set of keys and their types in + the result set. We use the DataType subclasses for those introspected types + to generate the form and parse the form results. + """ + + PATH = DatastoreQueryHandler.PATH + '/edit' + + def get(self): + entity_key = self.request.get('key') + if entity_key: + key_instance = datastore.Key(entity_key) + entity_key_name = key_instance.name() + entity_key_id = key_instance.id() + namespace = key_instance.namespace() + parent_key = key_instance.parent() + kind = key_instance.kind() + entity = datastore.Get(key_instance) + sample_entities = [entity] + else: + kind = self.request.get('kind') + sample_entities = self.execute_query()[0] + + if len(sample_entities) < 1: + next_uri = self.request.get('next') + next_uri += '&msg=%s' % urllib.quote_plus( + "The kind %s doesn't exist in the %s namespace" % ( + kind, + self.request.get('namespace', ''))) + + kind_param = 'kind=%s' % kind + if not kind_param in next_uri: + if '?' in next_uri: + next_uri += '&' + kind_param + else: + next_uri += '?' + kind_param + self.redirect(next_uri) + return + + if not entity_key: + key_instance = None + entity_key_name = None + entity_key_id = None + namespace = self.request.get('namespace') + parent_key = None + entity = None + + if parent_key: + parent_kind = parent_key.kind() + parent_key_string = PseudoBreadcrumbs(parent_key) + else: + parent_kind = None + parent_key_string = None + + fields = [] + key_values = self.get_key_values(sample_entities) + for key, sample_values in key_values.iteritems(): + if entity and entity.has_key(key): + data_type = DataType.get(entity[key]) + else: + data_type = DataType.get(sample_values[0]) + name = data_type.name() + "|" + key + if entity and entity.has_key(key): + value = entity[key] + else: + value = None + field = data_type.input_field(name, value, sample_values) + fields.append((key, data_type.name(), field)) + + self.generate('datastore_edit.html', { + 'kind': kind, + 'key': entity_key, + 'key_name': entity_key_name, + 'key_id': entity_key_id, + 'fields': fields, + 'focus': self.request.get('focus'), + 'namespace': namespace, + 'next': self.request.get('next'), + 'parent_key': parent_key, + 'parent_kind': parent_kind, + 'parent_key_string': parent_key_string, + }) + + def post(self): + kind = self.request.get('kind') + entity_key = self.request.get('key') + if entity_key: + if self.request.get('action') == 'Delete': + datastore.Delete(datastore.Key(entity_key)) + self.redirect(self.request.get('next')) + return + entity = datastore.Get(datastore.Key(entity_key)) + else: + namespace = self.request.get('namespace') + if not namespace: + namespace = None + entity = datastore.Entity(kind, _namespace=namespace) + + args = self.request.arguments() + for arg in args: + bar = arg.find('|') + if bar > 0: + data_type_name = arg[:bar] + field_name = arg[bar + 1:] + form_value = self.request.get(arg) + data_type = DataType.get_by_name(data_type_name) + if entity and entity.has_key(field_name): + old_formatted_value = data_type.format(entity[field_name]) + if old_formatted_value == ustr(form_value): + continue + + if len(form_value) > 0: + value = data_type.parse(form_value) + entity[field_name] = value + elif entity.has_key(field_name): + del entity[field_name] + + datastore.Put(entity) + + self.redirect(self.request.get('next')) + + +class DataType(object): + """A DataType represents a data type in the datastore. + + Each DataType subtype defines four methods: + + format: returns a formatted string for a datastore value + input_field: returns a string HTML element for this DataType + name: the friendly string name of this DataType + parse: parses the formatted string representation of this DataType + python_type: the canonical Python type for this datastore type + + We use DataType instances to display formatted values in our result lists, + and we uses input_field/format/parse to generate forms and parse the results + from those forms to allow editing of entities. + """ + @staticmethod + def get(value): + return _DATA_TYPES[value.__class__] + + @staticmethod + def get_by_name(name): + return _NAMED_DATA_TYPES[name] + + def format(self, value): + return ustr(value) + + def short_format(self, value): + return self.format(value) + + def input_field(self, name, value, sample_values): + if value is not None: + string_value = self.format(value) + else: + string_value = '' + return '' % (cgi.escape(ustr(self.name())), cgi.escape(ustr(name)), + self.input_field_size(), + cgi.escape(string_value, True)) + + def input_field_size(self): + return 30 + + def additional_short_value_html(self, unused_value): + return '' + + +class StringType(DataType): + def format(self, value): + return ustr(value) + + def input_field(self, name, value, sample_values): + name = ustr(name) + value = ustr(value) + sample_values = [ustr(s) for s in sample_values] + multiline = False + if value: + multiline = len(value) > 255 or value.find('\n') >= 0 + if not multiline: + for sample_value in sample_values: + if sample_value and (len(sample_value) > 255 or + sample_value.find('\n') >= 0): + multiline = True + break + if multiline: + if not value: + value = '' + return '' % (cgi.escape(name), cgi.escape(value)) + else: + return DataType.input_field(self, name, value, sample_values) + + def name(self): + return 'string' + + def parse(self, value): + return value + + def python_type(self): + return str + + def input_field_size(self): + return 50 + + +class TextType(StringType): + def name(self): + return 'Text' + + def input_field(self, name, value, sample_values): + return '' % (cgi.escape(ustr(name)), cgi.escape(ustr(value))) + + def parse(self, value): + return datastore_types.Text(value) + + def python_type(self): + return datastore_types.Text + + +class BlobType(StringType): + def name(self): + return 'Blob' + + def input_field(self, name, value, sample_values): + return '<binary>' + + def format(self, value): + return '' + + def python_type(self): + return datastore_types.Blob + + +class TimeType(DataType): + _FORMAT = '%Y-%m-%d %H:%M:%S' + + def format(self, value): + return value.strftime(TimeType._FORMAT) + + def name(self): + return 'datetime' + + def parse(self, value): + return datetime.datetime(*(time.strptime(ustr(value), + TimeType._FORMAT)[0:6])) + + def python_type(self): + return datetime.datetime + + +class ListType(DataType): + def format(self, value): + value_file = cStringIO.StringIO() + try: + writer = csv.writer(value_file) + writer.writerow(map(ustr, value)) + return ustr(value_file.getvalue()) + finally: + value_file.close() + + def name(self): + return 'list' + + def parse(self, value): + value_file = cStringIO.StringIO(ustr(value)) + try: + reader = csv.reader(value_file) + fields = [] + for field in reader.next(): + if isinstance(field, str): + field = field.decode('utf-8') + fields.append(field) + return fields + finally: + value_file.close() + + def python_type(self): + return list + + +class BoolType(DataType): + def name(self): + return 'bool' + + def input_field(self, name, value, sample_values): + selected = { None: '', False: '', True: '' }; + selected[value] = "selected" + return """""" % (cgi.escape(self.name()), cgi.escape(name), selected[None], + selected[False], selected[True]) + + def parse(self, value): + if value.lower() is 'true': + return True + if value.lower() is 'false': + return False + return bool(int(value)) + + def python_type(self): + return bool + + +class NumberType(DataType): + def input_field_size(self): + return 10 + + +class IntType(NumberType): + def name(self): + return 'int' + + def parse(self, value): + return int(value) + + def python_type(self): + return int + + +class LongType(NumberType): + def name(self): + return 'long' + + def parse(self, value): + return long(value) + + def python_type(self): + return long + + +class FloatType(NumberType): + def name(self): + return 'float' + + def parse(self, value): + return float(value) + + def python_type(self): + return float + + +class UserType(DataType): + def name(self): + return 'User' + + def parse(self, value): + return users.User(value) + + def python_type(self): + return users.User + + def input_field_size(self): + return 15 + + +class ReferenceType(DataType): + def name(self): + return 'Key' + + def short_format(self, value): + return str(value)[:8] + '...' + + def parse(self, value): + return datastore_types.Key(value) + + def python_type(self): + return datastore_types.Key + + def input_field(self, name, value, sample_values): + if value is not None: + string_value = self.format(value) + else: + string_value = '' + html = '' % (cgi.escape(self.name()), cgi.escape(name), self.input_field_size(), + cgi.escape(string_value, True)) + if value: + html += '
%s' % (cgi.escape(string_value, True), + cgi.escape(PseudoBreadcrumbs(value), True)) + return html + + def input_field_size(self): + return 85 + + def additional_short_value_html(self, value): + if not value: + return '' + return '
%s' % (cgi.escape(str(value), True), + cgi.escape(PseudoBreadcrumbs(value), True)) + + +class EmailType(StringType): + def name(self): + return 'Email' + + def parse(self, value): + return datastore_types.Email(value) + + def python_type(self): + return datastore_types.Email + + +class CategoryType(StringType): + def name(self): + return 'Category' + + def parse(self, value): + return datastore_types.Category(value) + + def python_type(self): + return datastore_types.Category + + +class LinkType(StringType): + def name(self): + return 'Link' + + def parse(self, value): + return datastore_types.Link(value) + + def python_type(self): + return datastore_types.Link + + +class GeoPtType(DataType): + def name(self): + return 'GeoPt' + + def parse(self, value): + return datastore_types.GeoPt(value) + + def python_type(self): + return datastore_types.GeoPt + + +class ImType(DataType): + def name(self): + return 'IM' + + def parse(self, value): + return datastore_types.IM(value) + + def python_type(self): + return datastore_types.IM + + +class PhoneNumberType(StringType): + def name(self): + return 'PhoneNumber' + + def parse(self, value): + return datastore_types.PhoneNumber(value) + + def python_type(self): + return datastore_types.PhoneNumber + + +class PostalAddressType(StringType): + def name(self): + return 'PostalAddress' + + def parse(self, value): + return datastore_types.PostalAddress(value) + + def python_type(self): + return datastore_types.PostalAddress + + +class RatingType(NumberType): + def name(self): + return 'Rating' + + def parse(self, value): + return datastore_types.Rating(value) + + def python_type(self): + return datastore_types.Rating + + +class NoneType(DataType): + def name(self): + return 'None' + + def parse(self, value): + return None + + def python_type(self): + return None + + def format(self, value): + return 'None' + + +class BlobKeyType(StringType): + def name(self): + return 'BlobKey' + + def parse(self, value): + return datastore_types.BlobKey(value) + + def python_type(self): + return datastore_types.BlobKey + + +_DATA_TYPES = { + types.NoneType: NoneType(), + types.StringType: StringType(), + types.UnicodeType: StringType(), + datastore_types.Text: TextType(), + datastore_types.Blob: BlobType(), + types.BooleanType: BoolType(), + types.IntType: IntType(), + types.LongType: LongType(), + types.FloatType: FloatType(), + datetime.datetime: TimeType(), + users.User: UserType(), + datastore_types.Key: ReferenceType(), + types.ListType: ListType(), + datastore_types.Email: EmailType(), + datastore_types.Category: CategoryType(), + datastore_types.Link: LinkType(), + datastore_types.GeoPt: GeoPtType(), + datastore_types.IM: ImType(), + datastore_types.PhoneNumber: PhoneNumberType(), + datastore_types.PostalAddress: PostalAddressType(), + datastore_types.Rating: RatingType(), + datastore_types.BlobKey: BlobKeyType(), + datastore_types.ByteString: StringType(), +} + +_NAMED_DATA_TYPES = {} +for data_type in _DATA_TYPES.values(): + _NAMED_DATA_TYPES[data_type.name()] = data_type + + +def _ParseCronYaml(): + """Loads the cron.yaml file and parses it. + + The CWD of the dev_appserver is the root of the application here. + + Returns a dict representing the contents of cron.yaml. + """ + cronyaml_files = 'cron.yaml', 'cron.yml' + for cronyaml in cronyaml_files: + try: + fh = open(cronyaml, "r") + except IOError: + continue + try: + cron_info = croninfo.LoadSingleCron(fh) + return cron_info + finally: + fh.close() + return None + + +def PseudoBreadcrumbs(key): + """Return a string that looks like the breadcrumbs (for key properties). + + Args: + key: A datastore_types.Key object. + + Returns: + A string looking like breadcrumbs. + """ + path = key.to_path() + parts = [] + for i in range(0, len(path)//2): + kind = path[i*2] + if isinstance(kind, unicode): + kind = kind.encode('utf8') + value = path[i*2 + 1] + if isinstance(value, (int, long)): + parts.append('%s: id=%d' % (kind, value)) + else: + if isinstance(value, unicode): + value = value.encode('utf8') + parts.append('%s: name=%s' % (kind, value)) + return ' > '.join(parts) + + +def main(): + handlers = [ + ('.*' + DatastoreQueryHandler.PATH, DatastoreQueryHandler), + ('.*' + DatastoreEditHandler.PATH, DatastoreEditHandler), + ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler), + ('.*' + InteractivePageHandler.PATH, InteractivePageHandler), + ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler), + ('.*' + MemcachePageHandler.PATH, MemcachePageHandler), + ('.*' + ImageHandler.PATH, ImageHandler), + ('.*' + QueuesPageHandler.PATH, QueuesPageHandler), + ('.*' + TasksPageHandler.PATH, TasksPageHandler), + ('.*' + XMPPPageHandler.PATH, XMPPPageHandler), + ('.*' + InboundMailPageHandler.PATH, InboundMailPageHandler), + ('.*', DefaultPageHandler), + ] + if HAVE_CRON: + handlers.insert(0, ('.*' + CronPageHandler.PATH, CronPageHandler)) + application = webapp.WSGIApplication(handlers, debug=_DEBUG) + wsgiref.handlers.CGIHandler().run(application) + + +import django +if django.VERSION[:2] < (0, 97): + from django.template import defaultfilters + def safe(text, dummy=None): + return text + defaultfilters.register.filter("safe", safe) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/ext/admin/templates/base.html b/google_appengine/google/appengine/ext/admin/templates/base.html new file mode 100644 index 0000000..fe71c39 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/base.html @@ -0,0 +1,94 @@ + + + + + {% block title %}{% endblock %} + + + + {% block head %}{% endblock %} + + +
+ +
+ +
+ +
+ +
+

{{ application_name }} Development Console

+
+ +
+ + +
+ +
+ +
+ +
+ + + +
+ +
+ +
+ {% block body %}{% endblock %} +
+ +
+ +
+

+ ©2009 Google +

+
+ {% block final %}{% endblock %} +
+ + + diff --git a/google_appengine/google/appengine/ext/admin/templates/cron.html b/google_appengine/google/appengine/ext/admin/templates/cron.html new file mode 100644 index 0000000..c692ae8 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/cron.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} + +{% block title %} +{{ application_name }} Development Console - Cron Viewer{% endblock %} + +{% block head %} + +{% endblock %} + +{% block breadcrumbs %} + Cron Viewer +{% endblock %} + +{% block body %} +

Cron Jobs

+ +{% if message %} +
+{{ message|escape }} +
+{% endif %} + +{% if cronjobs %} + + + + + + + + + + + + + {% for job in cronjobs %} + + + + + {% endfor %} + +
Cron JobSchedule
+

{{ job.url|escape }}

+

+ {{ job.description|escape }} +

+
+ + + + + +
+ {{ job.schedule|escape }} + + Test this job +
+ + {% if job.timezone %} + Timezone: {{ job.timezone }} +
+ Schedules with timezones won't be calculated correctly here. Use the + appcfg.py cron_info command to view the next run times for this schedule, + after installing the pytz package. +
+ {% endif %} +
+ In production, this would run at these times: +
    + {% for run in job.times %} +
  1. + {{ run.runtime }} {{ run.difference }} from now +
  2. + {% endfor %} +
+
+
+{% else %} + This application doesn't define any cron jobs. See the documentation for more. +{% endif %} + + +{% endblock %} + diff --git a/google_appengine/google/appengine/ext/admin/templates/css/ae.css b/google_appengine/google/appengine/ext/admin/templates/css/ae.css new file mode 100755 index 0000000..0e34b50 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/ae.css @@ -0,0 +1,170 @@ +/* Goog.css Overrides */ +h1 { + font-size: 1.5em; +} + +.g-doc { + width: auto; + margin: 0 10px; +} + +/* Header Selectors */ +#ae-logo { + margin-bottom: 0; +} +#ae-appbar-lrg { + margin: 0 0 1.25em 0; + padding: .2em .6em; + background-color: #e5ecf9; + border-top: 1px solid #36c; +} +#ae-appbar-lrg h1 { + margin: 0; + padding: 0; +} + +/* Footer Selectors */ +#ft p { + text-align: center; + margin-top: 2.5em; + padding-top: .5em; + border-top: 2px solid #c3d9ff; +} + +/* bd selectors */ +#bd h3 { + font-weight: bold; + font-size: 1.4em; +} +#bd p { + padding: 0 0 1em 0; +} +#ae-content { + padding-left: 1em; + border-left: 3px solid #e5ecf9; + min-height: 200px; +} + +/* Tables */ +.ae-table-plain { + border-collapse: collapse; + width: 100%; +} +.ae-table { + border: 1px solid #c5d7ef; + border-collapse: collapse; + width: 100%; +} + +#bd h2.ae-table-title { + background: #e5ecf9; + margin: 0; + color: #000; + font-size: 1em; + padding: 3px 0 3px 5px; + border-left: 1px solid #c5d7ef; + border-right: 1px solid #c5d7ef; + border-top: 1px solid #c5d7ef; +} +.ae-table-caption, +.ae-table caption { + border: 1px solid #c5d7ef; + background: #e5ecf9; + /** + * Fixes the caption margin ff display bug. + * see www.aurora-il.org/table_test.htm + * this is a slight variation to specifically target FF since Safari + * was shifting the caption over in an ugly fashion with margin-left: -1px + */ + -moz-margin-start: -1px; +} +.ae-table caption { + padding: 3px 5px; + text-align: left; +} +.ae-table th, +.ae-table td { + background-color: #fff; + padding: .35em 1em .25em .35em; + margin: 0; +} +.ae-table thead th { + font-weight: bold; + text-align: left; + background: #c5d7ef; + vertical-align: bottom; +} +.ae-table tfoot tr td { + border-top: 1px solid #c5d7ef; + background-color: #e5ecf9; +} +.ae-table td { + border-top: 1px solid #c5d7ef; + border-bottom: 1px solid #c5d7ef; +} +.ae-even td, +.ae-even th, +.ae-even-top td, +.ae-even-tween td, +.ae-even-bottom td, +ol.ae-even { + background-color: #e9e9e9; + border-top: 1px solid #c5d7ef; + border-bottom: 1px solid #c5d7ef; +} +.ae-even-top td { + border-bottom: 0; +} +.ae-even-bottom td { + border-top: 0; +} +.ae-even-tween td { + border: 0; +} +.ae-table .ae-tween td { + border: 0; +} +.ae-table .ae-tween-top td { + border-bottom: 0; +} +.ae-table .ae-tween-bottom td { + border-top: 0; +} +.ae-table #ae-live td { + background-color: #ffeac0; +} +.ae-table-fixed { + table-layout: fixed; +} +.ae-table-fixed td, +.ae-table-nowrap { + overflow: hidden; + white-space: nowrap; +} +.ae-new-usr td { + border-top: 1px solid #ccccce; + background-color: #ffe; +} +.ae-error-td td { + border: 2px solid #f00; + background-color: #fee; +} +.ae-table .ae-pager { + background-color: #c5d7ef; +} + +.ae-errorbox { + border: 1px solid #f00; + background-color: #fee; + margin-bottom: 1em; + padding: 1em; + display: inline-block; +} + +.ae-message { + border: 1px solid #e5ecf9; + background-color: #f6f9ff; + margin-bottom: 1em; + padding: 1em; + display: inline-block; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/base.css b/google_appengine/google/appengine/ext/admin/templates/css/base.css new file mode 100755 index 0000000..e326283 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/base.css @@ -0,0 +1,2 @@ +/* Copyright 2008 Google, Inc. All Rights Reserved */ +html,body,div,h1,h2,h3,h4,h5,h6,p,img,dl,dt,dd,ol,ul,li,table,caption,tbody,tfoot,thead,tr,th,td,form,fieldset,embed,object,applet{margin:0;padding:0;border:0}body{font-size:62.5%;font-family:Arial,sans-serif;color:#000;background:#fff}a{color:#00c}a:active{color:#f00}a:visited{color:#551a8b}table{border-collapse:collapse;border-width:0;empty-cells:show}ul{padding:0 0 1em 1em}ol{padding:0 0 1em 1.3em}li{line-height:1.5em;padding:0 0 .5em 0}p{padding:0 0 1em 0}h1,h2,h3,h4,h5{padding:0 0 1em 0}h1,h2{font-size:1.3em}h3{font-size:1.1em}h4,h5,table{font-size:1em}sup,sub{font-size:.7em}input,select,textarea,option{font-family:inherit;font-size:inherit}.g-doc,.g-doc-1024,.g-doc-800{font-size:130%}.g-doc{width:100%;text-align:left}.g-section:after{content:".";display:block;height:0;clear:both;visibility:hidden}.g-unit .g-section:after{clear:none}.g-unit .g-section{width:100%;overflow:hidden}.g-section,.g-unit{zoom:1}.g-split .g-unit{text-align:right}.g-split .g-first{text-align:left}.g-tpl-25-75 .g-unit,.g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75 .g-unit{width:74.999%;float:right;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-tpl-25-75 .g-first,.g-tpl-25-75 .g-first{width:24.999%;float:left;display:inline;margin:0}.g-tpl-25-75-alt .g-unit,.g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-unit{width:24.999%;float:left;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-tpl-25-75-alt .g-first,.g-tpl-25-75-alt .g-first{width:74.999%;float:right;display:inline;margin:0}.g-tpl-75-25 .g-unit,.g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25 .g-unit{width:24.999%;float:right;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-tpl-75-25 .g-first,.g-tpl-75-25 .g-first{width:74.999%;float:left;display:inline;margin:0}.g-tpl-75-25-alt .g-unit,.g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-unit{width:74.999%;float:left;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-tpl-75-25-alt .g-first,.g-tpl-75-25-alt .g-first{width:24.999%;float:right;display:inline;margin:0}.g-tpl-33-67 .g-unit,.g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67 .g-unit{width:66.999%;float:right;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-tpl-33-67 .g-first,.g-tpl-33-67 .g-first{width:32.999%;float:left;display:inline;margin:0}.g-tpl-33-67-alt .g-unit,.g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-unit{width:32.999%;float:left;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-tpl-33-67-alt .g-first,.g-tpl-33-67-alt .g-first{width:66.999%;float:right;display:inline;margin:0}.g-tpl-67-33 .g-unit,.g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33 .g-unit{width:32.999%;float:right;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-tpl-67-33 .g-first,.g-tpl-67-33 .g-first{width:66.999%;float:left;display:inline;margin:0}.g-tpl-67-33-alt .g-unit,.g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-unit{width:66.999%;float:left;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-tpl-67-33-alt .g-first,.g-tpl-67-33-alt .g-first{width:32.999%;float:right;display:inline;margin:0}.g-tpl-50-50 .g-unit,.g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50 .g-unit{width:49.999%;float:right;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-tpl-50-50 .g-first,.g-tpl-50-50 .g-first{width:49.999%;float:left;display:inline;margin:0}.g-tpl-50-50-alt .g-unit,.g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-unit{width:49.999%;float:left;display:inline;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-tpl-50-50-alt .g-first,.g-tpl-50-50-alt .g-first{width:49.999%;float:right;display:inline;margin:0}.g-tpl-nest .g-unit,.g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest .g-unit{float:left;width:auto;display:inline;margin:0}.g-tpl-nest-alt .g-unit,.g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest-alt .g-unit{float:right;width:auto;display:inline;margin:0}.g-doc-1024{width:73.074em;*width:71.313em;min-width:950px;margin:0 auto;text-align:left}.g-doc-800{width:57.69em;*width:56.3em;min-width:750px;margin:0 auto;text-align:left}.g-tpl-160 .g-unit,.g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-unit .g-tpl-160 .g-unit{display:block;margin:0 0 0 161px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-tpl-160 .g-first,.g-tpl-160 .g-first{display:block;margin:0;width:161px;float:left}.g-tpl-160-alt .g-unit,.g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-160-alt .g-unit{display:block;margin:0 161px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-tpl-160-alt .g-first,.g-tpl-160-alt .g-first{display:block;margin:0;width:161px;float:right}.g-tpl-180 .g-unit,.g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-unit .g-tpl-180 .g-unit{display:block;margin:0 0 0 181px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-tpl-180 .g-first,.g-tpl-180 .g-first{display:block;margin:0;width:181px;float:left}.g-tpl-180-alt .g-unit,.g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-180-alt .g-unit{display:block;margin:0 181px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-tpl-180-alt .g-first,.g-tpl-180-alt .g-first{display:block;margin:0;width:181px;float:right}.g-tpl-300 .g-unit,.g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-unit .g-tpl-300 .g-unit{display:block;margin:0 0 0 301px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-tpl-300 .g-first,.g-tpl-300 .g-first{display:block;margin:0;width:301px;float:left}.g-tpl-300-alt .g-unit,.g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-300-alt .g-unit{display:block;margin:0 301px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-tpl-300-alt .g-first,.g-tpl-300-alt .g-first{display:block;margin:0;width:301px;float:right} \ No newline at end of file diff --git a/google_appengine/google/appengine/ext/admin/templates/css/cron.css b/google_appengine/google/appengine/ext/admin/templates/css/cron.css new file mode 100644 index 0000000..679d358 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/cron.css @@ -0,0 +1,26 @@ +.ah-cron-message { + color: red; + margin-bottom: 1em; +} + +#ah-cron-jobs .ah-cron-message { + margin: 1em; +} + +.ah-cron-times { + margin-top: 1em; +} +#ah-cron-jobs .ae-table, +#ah-cron-jobs .ae-table td { + border: 0; + padding: 0; +} +#ah-cron-jobs ol { + list-style: none; +} +#ah-cron-jobs li { + padding: .2em 0; +} +.ah-cron-test { + text-align: right; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/datastore.css b/google_appengine/google/appengine/ext/admin/templates/css/datastore.css new file mode 100644 index 0000000..f2f9f1d --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/datastore.css @@ -0,0 +1,71 @@ +#datastore_search { + margin-bottom: 1em; +} + +#hint { + background-color: #F6F9FF; + border: 1px solid #E5ECF9; + margin-bottom: 1em; + padding: 0.5em 1em; +} + +#message { + color: red; + position: relative; + bottom: 6px; +} + +#pagetotal { + float: right; +} + +#pagetotal .count { + font-weight: bold; +} + +table.entities { + border: 1px solid #c5d7ef; + border-collapse: collapse; + width: 100%; + margin-bottom: 0; +} + +table.entities th, table.entities td { + padding: .25em 1.5em .5em .5em; +} + +table.entities th { + font-weight: bold; + text-align: left; + background: #e5ecf9; + white-space: nowrap; +} + +table.entities th a, table.entities th a:visited { + color: black; + text-decoration: none; +} + +table.entities td { + background-color: #fff; + text-align: left; + vertical-align: top; + cursor: pointer; +} + +table.entities tr.even td { + background-color: #f9f9f9; +} + +div.entities { + background-color: #c5d7ef; + margin-top: 0; +} + +#entities-pager, #entities-control { + padding: .3em 1em .4em 1em; +} + +#entities-pager { + text-align: right; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/form.css b/google_appengine/google/appengine/ext/admin/templates/css/form.css new file mode 100644 index 0000000..0f8e2e0 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/form.css @@ -0,0 +1,20 @@ +table.form { + border-collapse: collapse; +} + +table.form td.name, table.form td.value, table.form td.buttons { + border: 0; + padding: 7px; + padding-left: 0; + vertical-align: top; +} + +table.form td.name { + font-weight: bold; + padding-top: 9px; + padding-right: 14px; +} + +table.form td.buttons { + padding-top: 12px; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/inboundmail.css b/google_appengine/google/appengine/ext/admin/templates/css/inboundmail.css new file mode 100644 index 0000000..7318a4e --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/inboundmail.css @@ -0,0 +1,19 @@ +#inboundmail label { + display: block; + font-weight: bold; +} +#inboundmail legend { + font-weight: bold; +} +#inboundmail .radio label { + display: inline; + font-weight: normal; +} + +#inboundmail fieldset, +#inboundmail .fieldset { + margin-bottom: 8px; +} +#inboundmail-submit { + margin-top: 2em; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/memcache.css b/google_appengine/google/appengine/ext/admin/templates/css/memcache.css new file mode 100644 index 0000000..729c871 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/memcache.css @@ -0,0 +1,54 @@ +.message { + color: red; + margin-bottom: 1em; +} + +#flush_form { + display: inline; + margin-left: 2em; +} + +#memcache_search { + margin-bottom: 2em; +} + +#value_display { + border: 1px solid #c5d7ef; +} + +#value_display_key { + text-align: left; + padding: 1ex; + background: #e5ecf9; +} + +#value_display_value { + height: 20em; + margin: 0; + padding: 1ex; + background: #f9f9f9; + font-family: monospace; + overflow: auto; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + white-space: pre-wrap; + word-wrap: break-word; +} + +#memcache_edit th { + font-weight: bold; + padding: 2ex 3ex 0 0; +} + +#memcache_edit td { + padding: 2ex 0 0 0; +} + +#memcache_edit th#value_key { + vertical-align: top; +} + +#memcache_edit div#value_key_text { + padding-top: 3px; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/nav.css b/google_appengine/google/appengine/ext/admin/templates/css/nav.css new file mode 100755 index 0000000..6a3cb39 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/nav.css @@ -0,0 +1,88 @@ +#ae-nav ul { + list-style-type: none; + margin: 0; + padding: 1em 0; +} +#ae-nav ul li { + padding-left: .5em; +} + +#ae-nav .ae-nav-selected { + color: #000; + display: block; + font-weight: bold; + background-color: #e5ecf9; + border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; +} + +a.ae-nav-selected { + color: #000; + text-decoration:none; +} + +/* aka disabled items */ +#ae-nav ul li span.ae-nav-disabled { + color: #666; +} + +/* Sub-navigation rules */ +#ae-nav ul ul { + margin: 0; + padding: 0 0 0 .5em; +} +#ae-nav ul ul li { + padding-left: .5em; +} +#ae-nav ul li a, +#ae-nav ul li span, +#ae-nav ul ul li a { + padding-left: .5em; +} + +/* ae-nav Link Selectors */ +#ae-nav li a:link, +#ae-nav li a:visited { + color: #00c; +} +#ae-nav li a:link.ae-nav-selected, +#ae-nav li a:visited.ae-nav-selected { + color: #000; + text-decoration: none; +} + +/* Group of boxed help links */ +.ae-nav-group { + padding: .5em; + margin: 0 .75em 0 0; + background-color: #fffbe8; + border: 1px solid #fff1a9; +} +.ae-nav-group h4 { + font-weight: bold; + padding: auto auto .5em .5em; + padding-left: .4em; + margin-bottom: .5em; + padding-bottom: 0; +} +.ae-nav-group ul { + margin: 0 0 .5em 0; + padding: 0 0 0 1.3em; + list-style-type: none; +} +.ae-nav-group ul li { + padding-bottom: .5em; +} + +/* ae-nav-group link Selectors */ +.ae-nav-group li a:link, +.ae-nav-group li a:visited { + color: #00c; +} +.ae-nav-group li a:hover { + color: #00c; +} \ No newline at end of file diff --git a/google_appengine/google/appengine/ext/admin/templates/css/pager.css b/google_appengine/google/appengine/ext/admin/templates/css/pager.css new file mode 100644 index 0000000..393ce46 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/pager.css @@ -0,0 +1,7 @@ +.ae-page-number { + margin: 0 0.5em; +} + +.ae-page-selected { + font-weight: bold; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/queues.css b/google_appengine/google/appengine/ext/admin/templates/css/queues.css new file mode 100644 index 0000000..35bf035 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/queues.css @@ -0,0 +1,26 @@ +.ah-queues-message { + color: red; + margin-bottom: 1em; +} + +#ah-queues .ah-queues-message { + margin: 1em; +} + +.ah-queues-times { + margin-top: 1em; +} +#ah-queues .ae-table, +#ah-queues .ae-table td { + border: 0; + padding: 0; +} +#ah-queues ol { + list-style: none; +} +#ah-queues li { + padding: .2em 0; +} +.ah-queues-test { + text-align: right; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/tasks.css b/google_appengine/google/appengine/ext/admin/templates/css/tasks.css new file mode 100644 index 0000000..811f728 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/tasks.css @@ -0,0 +1,26 @@ +.ah-tasks-message { + color: red; + margin-bottom: 1em; +} + +#ah-tasks .ah-tasks-message { + margin: 1em; +} + +.ah-task-times { + margin-top: 1em; +} +#ah-tasks .ae-table, +#ah-tasks .ae-table td { + border: 0; + padding: 0; +} +#ah-tasks ol { + list-style: none; +} +#ah-tasks li { + padding: .2em 0; +} +.ah-task-test { + text-align: right; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/css/xmpp.css b/google_appengine/google/appengine/ext/admin/templates/css/xmpp.css new file mode 100644 index 0000000..94f6647 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/css/xmpp.css @@ -0,0 +1,19 @@ +#xmpp label { + display: block; + font-weight: bold; +} +#xmpp legend { + font-weight: bold; +} +#xmpp .radio label { + display: inline; + font-weight: normal; +} + +#xmpp fieldset, +#xmpp .fieldset { + margin-bottom: 8px; +} +#xmpp-submit { + margin-top: 2em; +} diff --git a/google_appengine/google/appengine/ext/admin/templates/datastore.html b/google_appengine/google/appengine/ext/admin/templates/datastore.html new file mode 100644 index 0000000..b8f5d6a --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/datastore.html @@ -0,0 +1,243 @@ +{% extends "base.html" %} + +{% block title %}{{ application_name }} Development Console - Datastore Viewer{% endblock %} + +{% block head %} + + + +{% endblock %} + +{% block body %} +

Datastore Viewer

+ + {% if in_production %} +
+ The Admin Console Data Viewer + allows you to run GQL queries and much more! +
+ {% endif %} + + {% if message %} +
+ {{ message }} +
+ {% endif %} + + {% if entities %} +
+ Results {{ start|add:1 }} - {{ entities|length|add:start }} of {{ total }} +
+ {% endif %} + +
+ {% if kinds or in_production %} + + {% else %} +
+ {% if namespace %} + Datastore has no entities in namespace + "{{ namespace|escape }}". + {% else %} + Datastore has no entities in the Empty namespace. You need to + add data programatically before you can use this tool to view + and edit it. + {% endif %} +
+ {% if show_namespace %} + + + + + {% else %} + Select different namespace + {% endif %} +
+ {% endif %} +
+ + {% if entities %} +
+ + + + + + + + + + + + {% for header in headers %} + + {% endfor %} + + {% for entity in entities %} + + + + + + {% for attribute in entity.attributes %} + + {% endfor %} + + {% endfor %} +
KeyIDKey Name{{ header.name }}
{{ entity.shortened_key|escape }} + {% if entity.key_id %} + {{entity.key_id}} + {% endif %} + + {% if entity.key_name %} + {{entity.key_name}} + {% endif %} + {{ attribute.short_value|truncatewords:20|escape }}{{ attribute.additional_html|safe }}
+
+
+
+
+
+
+ {% if pages %} + {% include "pager.html" %} + {% endif %} +
+
+
+
+ {% else %} + {% if kind and kinds %} +

+ Datastore contains no entities of kind "{{ kind|escape }}" in the + {% if namespace %} + namespace "{{ namespace|escape }}". + {% else %} + Empty namespace. + {% endif %} +

+ {% endif %} + {% endif %} +{% endblock %} + +{% block final %} + +{% endblock %} + + diff --git a/google_appengine/google/appengine/ext/admin/templates/datastore_edit.html b/google_appengine/google/appengine/ext/admin/templates/datastore_edit.html new file mode 100644 index 0000000..0763940 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/datastore_edit.html @@ -0,0 +1,184 @@ +{% extends "base.html" %} + +{% block title %}{{ application_name }} Development Console - Datastore Viewer - {% if key %}Edit Entity{% else %}New Entity{% endif %}{% endblock %} + +{% block head %} + + + +{% endblock %} + +{% block bodyattributes %}onload="load()"{% endblock %} + +{% block body %} +

{% if key %}Edit Entity{% else %}New Entity{% endif %}

+ +
+
+ + {% if key %} + {% if namespace %} + + + + + {% endif %} + {% endif %} + + + + + {% if key %} + + + + + {% endif %} + {% if key_name %} + + + + + {% endif %} + {% if key_id %} + + + + + {% endif %} + {% if parent_key %} + + + + + {% endif %} + {% if not key %} + {% if namespace %} + + + + + {% endif %} + {% endif %} + {% for field in fields %} + + + + + {% endfor %} + + + + +
+ Namespace + {{ namespace|escape }}
Entity Kind + {{ kind }} + +
Entity Key + {{ key }} + +
Key Name + {{ key_name }} +
ID + {{ key_id }} +
Parent + {{ parent_key|escape }}
+ {{ parent_key_string }} +
+ Namespace + ({{ namespace|escape }}) +
+ {{ field.0|escape }} + ({{ field.1|escape }}) +
{{ field.2|safe }}
+ + {% if key %} + + {% endif %} +
+
+ +
+{% endblock %} + +{% block final %} + +{% endblock %} diff --git a/google_appengine/google/appengine/ext/admin/templates/images/google.gif b/google_appengine/google/appengine/ext/admin/templates/images/google.gif new file mode 100755 index 0000000000000000000000000000000000000000..5e9d2f36ee4b34c0ab6bfc0d0ff954ca9f7e0c71 GIT binary patch literal 1470 zcwPa%1wr~pNk%w1VVM9g0M!5ht;p%yq@3jB<1}~5u&}aZm)8Bly8HY4NrulD-36y1uo?sSG@*>+$`{y@U4r{^Z3o@%8+? z$Jzb*^r*h+f2ZE`^YPEk%w~hY$w@bwou$yx)vu_eQgprR?dpS>$Z(kFc#+7OyW}Qr z#{d8SA^8LV00000EC2ui0GR+U000L6z@KnPEE^5nEDIn!Gm#(*ieUmK z8yhBsi)RozGY%Xz95V_&I3-{QDiRwE5}ci65C{nb0x>QwIWh_-9I{;p53drlWg-X) zH#x#T5+f2WVZ{tK$YTc}3JH}(F1bW52IbE}9~~VPM=B^RC?X=nuMtD!1{EqlG3P%Q z1$t}&SfHRm2vYzQ08m8)4rWdaOwgc#1`P@O(2+=yLcVwy z`GW-v4kZZSIFX=)N_!GLNRTlhL<1G)gbwh*Fbhwha9$`t>W_$k2^1E%U=<()1$!hi zG$SMc0|pNVS`4VMtHs`=)&v{?j4g)-3lu~gK`_)u0U`5>BhXpE>SBtldV0#;;LVyV#Jlqgq1cK=wb44F#9dIr~x&6^q1W&y6U|?MU5+@)4to4SIKte&t zpMngnhCmOb;UxqB&5>9CgbEM@0s#s!jWA9C7BoPDc1aD9!*?>6QiBjK=y1Y}*7QOH z1vhw5f(#L`a1IOt5=qn#LLsnWa!4}yo0CKE%zXO=7gbVHv6 z7SKRm1%PotpJy3ppjihPj9`|l0?crN4mBNs2?J*3KmolINDx2(It_9107P89-%tyT zEELxxZ;JrBzLorBYX!_Ts@ViCfTBdHwAtJY^qJ5=0wWY20pOrD5DO8j88<}8Rg&fd#iiqf`nJ-JN-A;} zjO%)}EJ$hFTGd7fK)?tN^j-r260C*005Y^-Lj(ukV?*!}L{O7R2sl7M2{rtpg9|D| zAcGCJ_Aqy~4l(pPj-p2>ivJHPynsOF^gDnA z_Tnxi^koU_6IB=57O!b70tJH-hK*Fwg#>=83$^fx==gwwv7}@OEZ{)`9F`3tv~K~p z>%##`(40xM?^w+!p$w5g0J`A-2ON-qW@gAkItd^wz!ShXdPu|~8u5roOrjE($iyZ( Y@rh83q7Email +{% endblock %} + +{% block head %} + + + +{% endblock %} + +{% block body %} +
+

Email

+ {% if inboundmail_configured %}{% else %} +
+ Inbound mail is not yet configured properly in your app.yaml in the services section. +
+ {% endif %} +
+
+ + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+
+ +{% endblock %} + +{% block final %} +{% endblock %} diff --git a/google_appengine/google/appengine/ext/admin/templates/interactive-output.html b/google_appengine/google/appengine/ext/admin/templates/interactive-output.html new file mode 100644 index 0000000..8ecdc7b --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/interactive-output.html @@ -0,0 +1,36 @@ + + + + +Results + + + +
{{ output|escape }}
+ + diff --git a/google_appengine/google/appengine/ext/admin/templates/interactive.html b/google_appengine/google/appengine/ext/admin/templates/interactive.html new file mode 100644 index 0000000..78667e7 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/interactive.html @@ -0,0 +1,104 @@ +{% extends "base.html" %} + +{% block title %}{{ application_name }} Development Console - Interactive Console{% endblock %} + +{% block breadcrumbs %} + Interactive Console +{% endblock %} + +{% block head %} + +{% endblock %} + +{% block body %} +

Interactive Console

+
+ + + + + + + + +
+ + + +
+
+
+
+{% endblock %} + +{% block final %} + +{% endblock %} diff --git a/google_appengine/google/appengine/ext/admin/templates/js/multipart_form_data.js b/google_appengine/google/appengine/ext/admin/templates/js/multipart_form_data.js new file mode 100644 index 0000000..8b9706e --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/js/multipart_form_data.js @@ -0,0 +1,125 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +/** + * A multipart form data construction class for XHR. + * @see http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html + * @constructor + */ +var MultipartFormData = function() { + /** + * @type {Array} + */ + this.headers = []; + + /** + * @type {Array} + */ + this.parts = []; + + /** + * A random string for the boundary. + * @type {string} + */ + this.boundary = MultipartFormData.getRandomBoundary(); +}; + + +/** + * @type {string} + */ +MultipartFormData.CRLF = '\r\n'; + + +/** + * @type {string} + * @private + */ +MultipartFormData.TEN_CHARS_ = + + +/** + * Generates a random number and some random characters from it. + */ +MultipartFormData.getRandomBoundary = function() { + var anyTenCharacters = 'DiStRIcT10'; + var randomNumber = Math.floor(Math.random() * 10000000); + var nums = randomNumber.toString().split(''); + var randomChars = ''; + for (var i = 0, num; num = nums[i]; i++) { + randomChars += anyTenCharacters[num]; + } + return randomChars + '-' + randomNumber; +}; + + +/** + * @param {string} name The name for this header. + * @param {string} value The value for this header. + */ +MultipartFormData.prototype.addHeader = function(name, value) { + this.headers.push({ + 'name': name, + 'value': value + }); +}; + + +/** + * @param {?string} name The name for this part. + * @param {string} value The value for this part. + * @param {string} opt_contentType Content-type for this part. + * @param {string} opt_contentDisposition Content disposition for this part. + * @param {string} opt_filename The filename for this part + */ +MultipartFormData.prototype.addPart = function(name, value, opt_contentType, + opt_contentDisposition, opt_filename) { + var contentType = opt_contentType || null; + var contentDisposition = opt_contentDisposition || null; + var filename = opt_filename || null; + this.parts.push({ + 'name': name, + 'value': value, + 'contentType': contentType, + 'contentDisposition': contentDisposition, + 'filename': filename + }); +}; + +/** + * @return {string} The string to set as a payload. + */ +MultipartFormData.prototype.toString = function() { + var lines = []; + + for (var i = 0, header; header = this.headers[i]; i++) { + lines.push(header['name'] + ': ' + header['value']); + } + if (this.headers.length > 0) { + lines.push(''); + } + + for (var i = 0, part; part = this.parts[i]; i++) { + lines.push('--' + this.boundary); + + if (part['contentDisposition']) { + var contentDisposition = 'Content-Disposition: form-data; '; + contentDisposition += 'name="' + part['name'] + '"'; + if (part['filename']) { + contentDisposition += '; filename="' + part['filename'] + '"'; + } + lines.push(contentDisposition); + } + + if (part['contentType']) { + lines.push('Content-Type: ' + part['contentType']); + } + + lines.push(''); + lines.push(part['value']); + } + + lines.push('--' + this.boundary + '--'); + + return lines.join(MultipartFormData.CRLF) + MultipartFormData.CRLF; +}; + diff --git a/google_appengine/google/appengine/ext/admin/templates/js/rfc822_date.js b/google_appengine/google/appengine/ext/admin/templates/js/rfc822_date.js new file mode 100644 index 0000000..9037075 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/js/rfc822_date.js @@ -0,0 +1,70 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +var RFC822Date = {}; + +/** + * Return a DateTime in RFC822 format. + * @see http://www.w3.org/Protocols/rfc822/#z28 + * @param {Date} date A Date object. + * @param {string} opt_tzo The timezone offset. + */ +RFC822Date.format = function(date, opt_tzo) { + var tzo = opt_tzo || RFC822Date.getTZO(date.getTimezoneOffset()); + var rfc822Date = RFC822Date.DAYS[date.getDay()] + ', '; + rfc822Date += RFC822Date.padZero(date.getDate()) + ' '; + rfc822Date += RFC822Date.MONTHS[date.getMonth()] + ' '; + rfc822Date += date.getFullYear() + ' '; + rfc822Date += RFC822Date.padZero(date.getHours()) + ':'; + rfc822Date += RFC822Date.padZero(date.getMinutes()) + ':'; + rfc822Date += RFC822Date.padZero(date.getSeconds()) + ' ' ; + rfc822Date += tzo; + return rfc822Date; +}; + + +/** + * @type {Array} + */ +RFC822Date.MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + + +/** + * @type {Array} + */ +RFC822Date.DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + + +/** + * Pads a value with a 0 if it is less than 10; + * @param {number|string} + * @return {string} + */ +RFC822Date.padZero = function(val) { + val = val + ''; // cast into string + if (val.length < 2) { + val = '0' + val; + } + return val; +}; + + +/** + * Returns a timezone offset in the format +|-dddd. + * @param {String} tzo A time zone offset from GMT in minutes. + * @return {string} The time zone offset as a string. + */ +RFC822Date.getTZO = function(tzo) { + var hours = Math.floor(tzo / 60); + var tzoFormatted = hours > 0 ? '-' : '+'; + + var absoluteHours = Math.abs(hours); + tzoFormatted += absoluteHours < 10 ? '0' : ''; + tzoFormatted += absoluteHours; + + var moduloMinutes = Math.abs(tzo % 60); + tzoFormatted += moduloMinutes == 0 ? '00' : moduloMinutes + + return tzoFormatted; +}; + diff --git a/google_appengine/google/appengine/ext/admin/templates/js/webhook.js b/google_appengine/google/appengine/ext/admin/templates/js/webhook.js new file mode 100644 index 0000000..0c1453e --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/js/webhook.js @@ -0,0 +1,99 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +function Webhook(formId) { + this.formId = formId; + this.action = null; + this.headers = {}; + this.method = null; + this.payload = null; +}; + +Webhook.prototype.HEADER_KEY = 'header:'; + +Webhook.prototype.parse = function() { + var form = document.getElementById(this.formId); + if (form == null) { + return 'could not find form with id "' + this.formId + '"'; + } + this.action = form.action; + this.method = form.method; + for (var i = 0, n = form.elements.length; i < n; i++) { + var currentElement = form.elements[i]; + if (currentElement.tagName != 'INPUT' || + currentElement.type.toUpperCase() != 'HIDDEN') { + continue; + } + var key = currentElement.name; + var value = currentElement.value; + var headerIndex = key.indexOf(this.HEADER_KEY); + if (headerIndex == 0) { + var header = key.substr(this.HEADER_KEY.length); + if (this.headers[header] === undefined) { + this.headers[header] = [value]; + } else { + this.headers[header].push(value); + } + } else if (key == 'payload') { + this.payload = value; + } + } + + if (this.action == '') { + return 'action not found'; + } + if (this.method == '') { + return 'method not found'; + } + return ''; +}; + +Webhook.prototype.send = function(callback) { + var req = null; + if (window.XMLHttpRequest) { + req = new XMLHttpRequest(); + } else if (window.ActiveXObject) { + req = new ActiveXObject('MSXML2.XMLHTTP.3.0'); + } + + try { + req.open(this.method, this.action, false); + for (var key in this.headers) { + // According to the W3C, multiple calls to setRequestHeader should result + // in a single header with comma-seperated values being set (see + // http://www.w3.org/TR/2009/WD-XMLHttpRequest-20090820/). Unfortunately, + // both FireFox 3 and Konqueror 3.5 set the header value to the value in + // the last call to setRequestHeader so the joined header is generated + // manually. The equivalence of headers with comma-separated values and + // repeated headers is described here: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + req.setRequestHeader(key, this.headers[key].join(', ')); + } + req.send(this.payload); + } catch (e) { + callback(this, req, e); + return; + } + + // If the responseText matches our
{% include "css/memcache.css" %} +{% endblock %} + +{% block breadcrumbs %} + Memcache Viewer +{% endblock %} + +{% block body %} +

Memcache Viewer

+ +{% if message %} +
+{{ message|escape }} +
+{% endif %} + +{% if show_stats %} +
+
    +
  • Hit ratio: {{ hitratio }}% ({{ stats.hits }} hit{{ stats.hits|pluralize }} and {{ stats.misses }} miss{{ stats.misses|pluralize:"es" }})
  • +
  • Size of cache: {{ stats.items }} item{{ stats.items|pluralize }}, {{ stats.bytes|filesizeformat }} + + +
  • + +
  • Cache contains items up to {{ oldest_item_age|timesince }} old.
  • +
+
+ + +{% endif %} + +{% if show_value %} +{% if key_exists %} +{% ifequal type "error" %} +
Error fetching {{ key|escape }}: {{ value|escape }}
+{% else %} +
+
"{{ key|escape }}" is a {{ type|escape }}:
+
{{ value|escape }}
+
+{% endifequal %} +{% else %} +
No such key: {{ key|escape }}
+{% endif %} +{% endif %} + +{% if show_valueform %} +
+
+ + + + + + + + + + + + + + + + + +
Key + + {{ key|escape }} +
Type + {% if key_exists %} + + {{ type|escape }} + {% else %} + + {% endif %} +
Value
+ +
  + {% if writable %} + + {% endif %} + +
+
+
+{% endif %} + +{% endblock %} + +{% block final %} + +{% endblock %} diff --git a/google_appengine/google/appengine/ext/admin/templates/pager.html b/google_appengine/google/appengine/ext/admin/templates/pager.html new file mode 100644 index 0000000..6c3ffa2 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/pager.html @@ -0,0 +1,9 @@ +{% ifnotequal prev_start -1 %}‹ Previous{% endifnotequal %} +  +{% for page in pages %} + {{ page.number }} +{% endfor %} +  +{% ifnotequal next_start -1 %}Next ›{% endifnotequal %} + diff --git a/google_appengine/google/appengine/ext/admin/templates/queues.html b/google_appengine/google/appengine/ext/admin/templates/queues.html new file mode 100644 index 0000000..5eccfa3 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/queues.html @@ -0,0 +1,75 @@ +{% extends "base.html" %} + +{% block title %} +{{ application_name }} Development Console - Task Queue Viewer{% endblock %} + +{% block head %} + +{% endblock %} + +{% block breadcrumbs %} + Queue Viewer +{% endblock %} + +{% block body %} +

Task Queues

+ +{% if queues %} +

+ Select a queue to run tasks manually. +

+ + + + + + + + + + + + + + {% for queue in queues %} + + + + + + + + + {% endfor %} + +
Queue NameMaximum RateBucket SizeOldest Task (UTC)Tasks in Queue
+ + {{ queue.name|escape }} + + {{ queue.max_rate|escape }} + + {{ queue.bucket_size|escape }} + + {% if queue.oldest_task %} + {{ queue.oldest_task|escape }}
+ ({{ queue.eta_delta|escape }}) + {% else %} + None + {% endif %} +
+ {{ queue.tasks_in_queue|escape }} + +
+ + +
+
+{% else %} + This application doesn't define any task queues. See the documentation for more. +{% endif %} + + +{% endblock %} + diff --git a/google_appengine/google/appengine/ext/admin/templates/tasks.html b/google_appengine/google/appengine/ext/admin/templates/tasks.html new file mode 100644 index 0000000..b841fba --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/tasks.html @@ -0,0 +1,103 @@ +{% extends "base.html" %} + +{% block title %} +{{ application_name }} Development Console - Tasks Viewer{% endblock %} + +{% block head %} + + + +{% endblock %} + +{% block breadcrumbs %} + Tasks Viewer +{% endblock %} + +{% block body %} +

Tasks for Queue: {{ queue_name|escape }}

+ +{% if tasks %} +

+ Push the 'Run' button to execute a task manually. +

+ + + + + + + + + + + + + + {% for task in tasks %} + + + + + + + + + {% endfor %} + + + + +
Task NameETA (UTC)MethodURL
+ {{ task.name|escape }} + + {{ task.eta|escape }} ({{ task.eta_delta|escape }}) + + {{ task.method|escape }} + + {{ task.url|escape }} + +
+ + {% for header in task.headers %} + + {% endfor %} + +
+
+
+ + + + +
+
+ {% include "pager.html" %} +
+ +{% else %} + This queue doesn't contain any tasks. +{% endif %} + + +{% endblock %} + diff --git a/google_appengine/google/appengine/ext/admin/templates/xmpp.html b/google_appengine/google/appengine/ext/admin/templates/xmpp.html new file mode 100644 index 0000000..629a473 --- /dev/null +++ b/google_appengine/google/appengine/ext/admin/templates/xmpp.html @@ -0,0 +1,234 @@ +{% extends "base.html" %} + +{% block title %}{{ application_name }} Development Console - XMPP{% endblock %} + +{% block breadcrumbs %} + XMPP +{% endblock %} + +{% block head %} + + +{% endblock %} + +{% block body %} +
+

XMPP

+ {% if xmpp_configured %}{% else %} +
+ XMPP is not yet configured properly in your app.yaml, in the services section. +
+ {% endif %} +
+
+ + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ +
+ +
+
+ + +{% endblock %} + +{% block final %} +{% endblock %} diff --git a/google_appengine/google/appengine/ext/appstats/__init__.py b/google_appengine/google/appengine/ext/appstats/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/ext/appstats/datamodel_pb.py b/google_appengine/google/appengine/ext/appstats/datamodel_pb.py new file mode 100755 index 0000000..b204cd9 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/datamodel_pb.py @@ -0,0 +1,1326 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +class AggregateRpcStatsProto(ProtocolBuffer.ProtocolMessage): + has_service_call_name_ = 0 + service_call_name_ = "" + has_total_amount_of_calls_ = 0 + total_amount_of_calls_ = 0 + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def service_call_name(self): return self.service_call_name_ + + def set_service_call_name(self, x): + self.has_service_call_name_ = 1 + self.service_call_name_ = x + + def clear_service_call_name(self): + if self.has_service_call_name_: + self.has_service_call_name_ = 0 + self.service_call_name_ = "" + + def has_service_call_name(self): return self.has_service_call_name_ + + def total_amount_of_calls(self): return self.total_amount_of_calls_ + + def set_total_amount_of_calls(self, x): + self.has_total_amount_of_calls_ = 1 + self.total_amount_of_calls_ = x + + def clear_total_amount_of_calls(self): + if self.has_total_amount_of_calls_: + self.has_total_amount_of_calls_ = 0 + self.total_amount_of_calls_ = 0 + + def has_total_amount_of_calls(self): return self.has_total_amount_of_calls_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_service_call_name()): self.set_service_call_name(x.service_call_name()) + if (x.has_total_amount_of_calls()): self.set_total_amount_of_calls(x.total_amount_of_calls()) + + def Equals(self, x): + if x is self: return 1 + if self.has_service_call_name_ != x.has_service_call_name_: return 0 + if self.has_service_call_name_ and self.service_call_name_ != x.service_call_name_: return 0 + if self.has_total_amount_of_calls_ != x.has_total_amount_of_calls_: return 0 + if self.has_total_amount_of_calls_ and self.total_amount_of_calls_ != x.total_amount_of_calls_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_service_call_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: service_call_name not set.') + if (not self.has_total_amount_of_calls_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: total_amount_of_calls not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.service_call_name_)) + n += self.lengthVarInt64(self.total_amount_of_calls_) + return n + 2 + + def Clear(self): + self.clear_service_call_name() + self.clear_total_amount_of_calls() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.service_call_name_) + out.putVarInt32(24) + out.putVarInt64(self.total_amount_of_calls_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_service_call_name(d.getPrefixedString()) + continue + if tt == 24: + self.set_total_amount_of_calls(d.getVarInt64()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_service_call_name_: res+=prefix+("service_call_name: %s\n" % self.DebugFormatString(self.service_call_name_)) + if self.has_total_amount_of_calls_: res+=prefix+("total_amount_of_calls: %s\n" % self.DebugFormatInt64(self.total_amount_of_calls_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kservice_call_name = 1 + ktotal_amount_of_calls = 3 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "service_call_name", + 3: "total_amount_of_calls", + }, 3) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.NUMERIC, + }, 3, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class KeyValProto(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + key_ = "" + has_value_ = 0 + value_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def set_key(self, x): + self.has_key_ = 1 + self.key_ = x + + def clear_key(self): + if self.has_key_: + self.has_key_ = 0 + self.key_ = "" + + def has_key(self): return self.has_key_ + + def value(self): return self.value_ + + def set_value(self, x): + self.has_value_ = 1 + self.value_ = x + + def clear_value(self): + if self.has_value_: + self.has_value_ = 0 + self.value_ = "" + + def has_value(self): return self.has_value_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.set_key(x.key()) + if (x.has_value()): self.set_value(x.value()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_value_ != x.has_value_: return 0 + if self.has_value_ and self.value_ != x.value_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + if (not self.has_value_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: value not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.key_)) + n += self.lengthString(len(self.value_)) + return n + 2 + + def Clear(self): + self.clear_key() + self.clear_value() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.key_) + out.putVarInt32(18) + out.putPrefixedString(self.value_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_key(d.getPrefixedString()) + continue + if tt == 18: + self.set_value(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: res+=prefix+("key: %s\n" % self.DebugFormatString(self.key_)) + if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kkey = 1 + kvalue = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "key", + 2: "value", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class StackFrameProto(ProtocolBuffer.ProtocolMessage): + has_class_or_file_name_ = 0 + class_or_file_name_ = "" + has_line_number_ = 0 + line_number_ = 0 + has_function_name_ = 0 + function_name_ = "" + + def __init__(self, contents=None): + self.variables_ = [] + if contents is not None: self.MergeFromString(contents) + + def class_or_file_name(self): return self.class_or_file_name_ + + def set_class_or_file_name(self, x): + self.has_class_or_file_name_ = 1 + self.class_or_file_name_ = x + + def clear_class_or_file_name(self): + if self.has_class_or_file_name_: + self.has_class_or_file_name_ = 0 + self.class_or_file_name_ = "" + + def has_class_or_file_name(self): return self.has_class_or_file_name_ + + def line_number(self): return self.line_number_ + + def set_line_number(self, x): + self.has_line_number_ = 1 + self.line_number_ = x + + def clear_line_number(self): + if self.has_line_number_: + self.has_line_number_ = 0 + self.line_number_ = 0 + + def has_line_number(self): return self.has_line_number_ + + def function_name(self): return self.function_name_ + + def set_function_name(self, x): + self.has_function_name_ = 1 + self.function_name_ = x + + def clear_function_name(self): + if self.has_function_name_: + self.has_function_name_ = 0 + self.function_name_ = "" + + def has_function_name(self): return self.has_function_name_ + + def variables_size(self): return len(self.variables_) + def variables_list(self): return self.variables_ + + def variables(self, i): + return self.variables_[i] + + def mutable_variables(self, i): + return self.variables_[i] + + def add_variables(self): + x = KeyValProto() + self.variables_.append(x) + return x + + def clear_variables(self): + self.variables_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_class_or_file_name()): self.set_class_or_file_name(x.class_or_file_name()) + if (x.has_line_number()): self.set_line_number(x.line_number()) + if (x.has_function_name()): self.set_function_name(x.function_name()) + for i in xrange(x.variables_size()): self.add_variables().CopyFrom(x.variables(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_class_or_file_name_ != x.has_class_or_file_name_: return 0 + if self.has_class_or_file_name_ and self.class_or_file_name_ != x.class_or_file_name_: return 0 + if self.has_line_number_ != x.has_line_number_: return 0 + if self.has_line_number_ and self.line_number_ != x.line_number_: return 0 + if self.has_function_name_ != x.has_function_name_: return 0 + if self.has_function_name_ and self.function_name_ != x.function_name_: return 0 + if len(self.variables_) != len(x.variables_): return 0 + for e1, e2 in zip(self.variables_, x.variables_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_class_or_file_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: class_or_file_name not set.') + if (not self.has_function_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: function_name not set.') + for p in self.variables_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.class_or_file_name_)) + if (self.has_line_number_): n += 1 + self.lengthVarInt64(self.line_number_) + n += self.lengthString(len(self.function_name_)) + n += 1 * len(self.variables_) + for i in xrange(len(self.variables_)): n += self.lengthString(self.variables_[i].ByteSize()) + return n + 2 + + def Clear(self): + self.clear_class_or_file_name() + self.clear_line_number() + self.clear_function_name() + self.clear_variables() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.class_or_file_name_) + if (self.has_line_number_): + out.putVarInt32(16) + out.putVarInt32(self.line_number_) + out.putVarInt32(26) + out.putPrefixedString(self.function_name_) + for i in xrange(len(self.variables_)): + out.putVarInt32(34) + out.putVarInt32(self.variables_[i].ByteSize()) + self.variables_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_class_or_file_name(d.getPrefixedString()) + continue + if tt == 16: + self.set_line_number(d.getVarInt32()) + continue + if tt == 26: + self.set_function_name(d.getPrefixedString()) + continue + if tt == 34: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_variables().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_class_or_file_name_: res+=prefix+("class_or_file_name: %s\n" % self.DebugFormatString(self.class_or_file_name_)) + if self.has_line_number_: res+=prefix+("line_number: %s\n" % self.DebugFormatInt32(self.line_number_)) + if self.has_function_name_: res+=prefix+("function_name: %s\n" % self.DebugFormatString(self.function_name_)) + cnt=0 + for e in self.variables_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("variables%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kclass_or_file_name = 1 + kline_number = 2 + kfunction_name = 3 + kvariables = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "class_or_file_name", + 2: "line_number", + 3: "function_name", + 4: "variables", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.NUMERIC, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class IndividualRpcStatsProto(ProtocolBuffer.ProtocolMessage): + has_service_call_name_ = 0 + service_call_name_ = "" + has_request_data_summary_ = 0 + request_data_summary_ = "" + has_response_data_summary_ = 0 + response_data_summary_ = "" + has_api_mcycles_ = 0 + api_mcycles_ = 0 + has_start_offset_milliseconds_ = 0 + start_offset_milliseconds_ = 0 + has_duration_milliseconds_ = 0 + duration_milliseconds_ = 0 + has_namespace_ = 0 + namespace_ = "" + has_was_successful_ = 0 + was_successful_ = 1 + + def __init__(self, contents=None): + self.call_stack_ = [] + if contents is not None: self.MergeFromString(contents) + + def service_call_name(self): return self.service_call_name_ + + def set_service_call_name(self, x): + self.has_service_call_name_ = 1 + self.service_call_name_ = x + + def clear_service_call_name(self): + if self.has_service_call_name_: + self.has_service_call_name_ = 0 + self.service_call_name_ = "" + + def has_service_call_name(self): return self.has_service_call_name_ + + def request_data_summary(self): return self.request_data_summary_ + + def set_request_data_summary(self, x): + self.has_request_data_summary_ = 1 + self.request_data_summary_ = x + + def clear_request_data_summary(self): + if self.has_request_data_summary_: + self.has_request_data_summary_ = 0 + self.request_data_summary_ = "" + + def has_request_data_summary(self): return self.has_request_data_summary_ + + def response_data_summary(self): return self.response_data_summary_ + + def set_response_data_summary(self, x): + self.has_response_data_summary_ = 1 + self.response_data_summary_ = x + + def clear_response_data_summary(self): + if self.has_response_data_summary_: + self.has_response_data_summary_ = 0 + self.response_data_summary_ = "" + + def has_response_data_summary(self): return self.has_response_data_summary_ + + def api_mcycles(self): return self.api_mcycles_ + + def set_api_mcycles(self, x): + self.has_api_mcycles_ = 1 + self.api_mcycles_ = x + + def clear_api_mcycles(self): + if self.has_api_mcycles_: + self.has_api_mcycles_ = 0 + self.api_mcycles_ = 0 + + def has_api_mcycles(self): return self.has_api_mcycles_ + + def start_offset_milliseconds(self): return self.start_offset_milliseconds_ + + def set_start_offset_milliseconds(self, x): + self.has_start_offset_milliseconds_ = 1 + self.start_offset_milliseconds_ = x + + def clear_start_offset_milliseconds(self): + if self.has_start_offset_milliseconds_: + self.has_start_offset_milliseconds_ = 0 + self.start_offset_milliseconds_ = 0 + + def has_start_offset_milliseconds(self): return self.has_start_offset_milliseconds_ + + def duration_milliseconds(self): return self.duration_milliseconds_ + + def set_duration_milliseconds(self, x): + self.has_duration_milliseconds_ = 1 + self.duration_milliseconds_ = x + + def clear_duration_milliseconds(self): + if self.has_duration_milliseconds_: + self.has_duration_milliseconds_ = 0 + self.duration_milliseconds_ = 0 + + def has_duration_milliseconds(self): return self.has_duration_milliseconds_ + + def namespace(self): return self.namespace_ + + def set_namespace(self, x): + self.has_namespace_ = 1 + self.namespace_ = x + + def clear_namespace(self): + if self.has_namespace_: + self.has_namespace_ = 0 + self.namespace_ = "" + + def has_namespace(self): return self.has_namespace_ + + def was_successful(self): return self.was_successful_ + + def set_was_successful(self, x): + self.has_was_successful_ = 1 + self.was_successful_ = x + + def clear_was_successful(self): + if self.has_was_successful_: + self.has_was_successful_ = 0 + self.was_successful_ = 1 + + def has_was_successful(self): return self.has_was_successful_ + + def call_stack_size(self): return len(self.call_stack_) + def call_stack_list(self): return self.call_stack_ + + def call_stack(self, i): + return self.call_stack_[i] + + def mutable_call_stack(self, i): + return self.call_stack_[i] + + def add_call_stack(self): + x = StackFrameProto() + self.call_stack_.append(x) + return x + + def clear_call_stack(self): + self.call_stack_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_service_call_name()): self.set_service_call_name(x.service_call_name()) + if (x.has_request_data_summary()): self.set_request_data_summary(x.request_data_summary()) + if (x.has_response_data_summary()): self.set_response_data_summary(x.response_data_summary()) + if (x.has_api_mcycles()): self.set_api_mcycles(x.api_mcycles()) + if (x.has_start_offset_milliseconds()): self.set_start_offset_milliseconds(x.start_offset_milliseconds()) + if (x.has_duration_milliseconds()): self.set_duration_milliseconds(x.duration_milliseconds()) + if (x.has_namespace()): self.set_namespace(x.namespace()) + if (x.has_was_successful()): self.set_was_successful(x.was_successful()) + for i in xrange(x.call_stack_size()): self.add_call_stack().CopyFrom(x.call_stack(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_service_call_name_ != x.has_service_call_name_: return 0 + if self.has_service_call_name_ and self.service_call_name_ != x.service_call_name_: return 0 + if self.has_request_data_summary_ != x.has_request_data_summary_: return 0 + if self.has_request_data_summary_ and self.request_data_summary_ != x.request_data_summary_: return 0 + if self.has_response_data_summary_ != x.has_response_data_summary_: return 0 + if self.has_response_data_summary_ and self.response_data_summary_ != x.response_data_summary_: return 0 + if self.has_api_mcycles_ != x.has_api_mcycles_: return 0 + if self.has_api_mcycles_ and self.api_mcycles_ != x.api_mcycles_: return 0 + if self.has_start_offset_milliseconds_ != x.has_start_offset_milliseconds_: return 0 + if self.has_start_offset_milliseconds_ and self.start_offset_milliseconds_ != x.start_offset_milliseconds_: return 0 + if self.has_duration_milliseconds_ != x.has_duration_milliseconds_: return 0 + if self.has_duration_milliseconds_ and self.duration_milliseconds_ != x.duration_milliseconds_: return 0 + if self.has_namespace_ != x.has_namespace_: return 0 + if self.has_namespace_ and self.namespace_ != x.namespace_: return 0 + if self.has_was_successful_ != x.has_was_successful_: return 0 + if self.has_was_successful_ and self.was_successful_ != x.was_successful_: return 0 + if len(self.call_stack_) != len(x.call_stack_): return 0 + for e1, e2 in zip(self.call_stack_, x.call_stack_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_service_call_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: service_call_name not set.') + if (not self.has_start_offset_milliseconds_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: start_offset_milliseconds not set.') + for p in self.call_stack_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.service_call_name_)) + if (self.has_request_data_summary_): n += 1 + self.lengthString(len(self.request_data_summary_)) + if (self.has_response_data_summary_): n += 1 + self.lengthString(len(self.response_data_summary_)) + if (self.has_api_mcycles_): n += 1 + self.lengthVarInt64(self.api_mcycles_) + n += self.lengthVarInt64(self.start_offset_milliseconds_) + if (self.has_duration_milliseconds_): n += 1 + self.lengthVarInt64(self.duration_milliseconds_) + if (self.has_namespace_): n += 1 + self.lengthString(len(self.namespace_)) + if (self.has_was_successful_): n += 2 + n += 1 * len(self.call_stack_) + for i in xrange(len(self.call_stack_)): n += self.lengthString(self.call_stack_[i].ByteSize()) + return n + 2 + + def Clear(self): + self.clear_service_call_name() + self.clear_request_data_summary() + self.clear_response_data_summary() + self.clear_api_mcycles() + self.clear_start_offset_milliseconds() + self.clear_duration_milliseconds() + self.clear_namespace() + self.clear_was_successful() + self.clear_call_stack() + + def OutputUnchecked(self, out): + out.putVarInt32(10) + out.putPrefixedString(self.service_call_name_) + if (self.has_request_data_summary_): + out.putVarInt32(26) + out.putPrefixedString(self.request_data_summary_) + if (self.has_response_data_summary_): + out.putVarInt32(34) + out.putPrefixedString(self.response_data_summary_) + if (self.has_api_mcycles_): + out.putVarInt32(40) + out.putVarInt64(self.api_mcycles_) + out.putVarInt32(48) + out.putVarInt64(self.start_offset_milliseconds_) + if (self.has_duration_milliseconds_): + out.putVarInt32(56) + out.putVarInt64(self.duration_milliseconds_) + if (self.has_namespace_): + out.putVarInt32(66) + out.putPrefixedString(self.namespace_) + if (self.has_was_successful_): + out.putVarInt32(72) + out.putBoolean(self.was_successful_) + for i in xrange(len(self.call_stack_)): + out.putVarInt32(82) + out.putVarInt32(self.call_stack_[i].ByteSize()) + self.call_stack_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + self.set_service_call_name(d.getPrefixedString()) + continue + if tt == 26: + self.set_request_data_summary(d.getPrefixedString()) + continue + if tt == 34: + self.set_response_data_summary(d.getPrefixedString()) + continue + if tt == 40: + self.set_api_mcycles(d.getVarInt64()) + continue + if tt == 48: + self.set_start_offset_milliseconds(d.getVarInt64()) + continue + if tt == 56: + self.set_duration_milliseconds(d.getVarInt64()) + continue + if tt == 66: + self.set_namespace(d.getPrefixedString()) + continue + if tt == 72: + self.set_was_successful(d.getBoolean()) + continue + if tt == 82: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_call_stack().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_service_call_name_: res+=prefix+("service_call_name: %s\n" % self.DebugFormatString(self.service_call_name_)) + if self.has_request_data_summary_: res+=prefix+("request_data_summary: %s\n" % self.DebugFormatString(self.request_data_summary_)) + if self.has_response_data_summary_: res+=prefix+("response_data_summary: %s\n" % self.DebugFormatString(self.response_data_summary_)) + if self.has_api_mcycles_: res+=prefix+("api_mcycles: %s\n" % self.DebugFormatInt64(self.api_mcycles_)) + if self.has_start_offset_milliseconds_: res+=prefix+("start_offset_milliseconds: %s\n" % self.DebugFormatInt64(self.start_offset_milliseconds_)) + if self.has_duration_milliseconds_: res+=prefix+("duration_milliseconds: %s\n" % self.DebugFormatInt64(self.duration_milliseconds_)) + if self.has_namespace_: res+=prefix+("namespace: %s\n" % self.DebugFormatString(self.namespace_)) + if self.has_was_successful_: res+=prefix+("was_successful: %s\n" % self.DebugFormatBool(self.was_successful_)) + cnt=0 + for e in self.call_stack_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("call_stack%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kservice_call_name = 1 + krequest_data_summary = 3 + kresponse_data_summary = 4 + kapi_mcycles = 5 + kstart_offset_milliseconds = 6 + kduration_milliseconds = 7 + knamespace = 8 + kwas_successful = 9 + kcall_stack = 10 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "service_call_name", + 3: "request_data_summary", + 4: "response_data_summary", + 5: "api_mcycles", + 6: "start_offset_milliseconds", + 7: "duration_milliseconds", + 8: "namespace", + 9: "was_successful", + 10: "call_stack", + }, 10) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.NUMERIC, + 7: ProtocolBuffer.Encoder.NUMERIC, + 8: ProtocolBuffer.Encoder.STRING, + 9: ProtocolBuffer.Encoder.NUMERIC, + 10: ProtocolBuffer.Encoder.STRING, + }, 10, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class RequestStatProto(ProtocolBuffer.ProtocolMessage): + has_start_timestamp_milliseconds_ = 0 + start_timestamp_milliseconds_ = 0 + has_http_method_ = 0 + http_method_ = "GET" + has_http_path_ = 0 + http_path_ = "/" + has_http_query_ = 0 + http_query_ = "" + has_http_status_ = 0 + http_status_ = 200 + has_duration_milliseconds_ = 0 + duration_milliseconds_ = 0 + has_api_mcycles_ = 0 + api_mcycles_ = 0 + has_processor_mcycles_ = 0 + processor_mcycles_ = 0 + has_overhead_walltime_milliseconds_ = 0 + overhead_walltime_milliseconds_ = 0 + has_user_email_ = 0 + user_email_ = "" + has_is_admin_ = 0 + is_admin_ = 0 + + def __init__(self, contents=None): + self.rpc_stats_ = [] + self.cgi_env_ = [] + self.individual_stats_ = [] + if contents is not None: self.MergeFromString(contents) + + def start_timestamp_milliseconds(self): return self.start_timestamp_milliseconds_ + + def set_start_timestamp_milliseconds(self, x): + self.has_start_timestamp_milliseconds_ = 1 + self.start_timestamp_milliseconds_ = x + + def clear_start_timestamp_milliseconds(self): + if self.has_start_timestamp_milliseconds_: + self.has_start_timestamp_milliseconds_ = 0 + self.start_timestamp_milliseconds_ = 0 + + def has_start_timestamp_milliseconds(self): return self.has_start_timestamp_milliseconds_ + + def http_method(self): return self.http_method_ + + def set_http_method(self, x): + self.has_http_method_ = 1 + self.http_method_ = x + + def clear_http_method(self): + if self.has_http_method_: + self.has_http_method_ = 0 + self.http_method_ = "GET" + + def has_http_method(self): return self.has_http_method_ + + def http_path(self): return self.http_path_ + + def set_http_path(self, x): + self.has_http_path_ = 1 + self.http_path_ = x + + def clear_http_path(self): + if self.has_http_path_: + self.has_http_path_ = 0 + self.http_path_ = "/" + + def has_http_path(self): return self.has_http_path_ + + def http_query(self): return self.http_query_ + + def set_http_query(self, x): + self.has_http_query_ = 1 + self.http_query_ = x + + def clear_http_query(self): + if self.has_http_query_: + self.has_http_query_ = 0 + self.http_query_ = "" + + def has_http_query(self): return self.has_http_query_ + + def http_status(self): return self.http_status_ + + def set_http_status(self, x): + self.has_http_status_ = 1 + self.http_status_ = x + + def clear_http_status(self): + if self.has_http_status_: + self.has_http_status_ = 0 + self.http_status_ = 200 + + def has_http_status(self): return self.has_http_status_ + + def duration_milliseconds(self): return self.duration_milliseconds_ + + def set_duration_milliseconds(self, x): + self.has_duration_milliseconds_ = 1 + self.duration_milliseconds_ = x + + def clear_duration_milliseconds(self): + if self.has_duration_milliseconds_: + self.has_duration_milliseconds_ = 0 + self.duration_milliseconds_ = 0 + + def has_duration_milliseconds(self): return self.has_duration_milliseconds_ + + def api_mcycles(self): return self.api_mcycles_ + + def set_api_mcycles(self, x): + self.has_api_mcycles_ = 1 + self.api_mcycles_ = x + + def clear_api_mcycles(self): + if self.has_api_mcycles_: + self.has_api_mcycles_ = 0 + self.api_mcycles_ = 0 + + def has_api_mcycles(self): return self.has_api_mcycles_ + + def processor_mcycles(self): return self.processor_mcycles_ + + def set_processor_mcycles(self, x): + self.has_processor_mcycles_ = 1 + self.processor_mcycles_ = x + + def clear_processor_mcycles(self): + if self.has_processor_mcycles_: + self.has_processor_mcycles_ = 0 + self.processor_mcycles_ = 0 + + def has_processor_mcycles(self): return self.has_processor_mcycles_ + + def rpc_stats_size(self): return len(self.rpc_stats_) + def rpc_stats_list(self): return self.rpc_stats_ + + def rpc_stats(self, i): + return self.rpc_stats_[i] + + def mutable_rpc_stats(self, i): + return self.rpc_stats_[i] + + def add_rpc_stats(self): + x = AggregateRpcStatsProto() + self.rpc_stats_.append(x) + return x + + def clear_rpc_stats(self): + self.rpc_stats_ = [] + def cgi_env_size(self): return len(self.cgi_env_) + def cgi_env_list(self): return self.cgi_env_ + + def cgi_env(self, i): + return self.cgi_env_[i] + + def mutable_cgi_env(self, i): + return self.cgi_env_[i] + + def add_cgi_env(self): + x = KeyValProto() + self.cgi_env_.append(x) + return x + + def clear_cgi_env(self): + self.cgi_env_ = [] + def overhead_walltime_milliseconds(self): return self.overhead_walltime_milliseconds_ + + def set_overhead_walltime_milliseconds(self, x): + self.has_overhead_walltime_milliseconds_ = 1 + self.overhead_walltime_milliseconds_ = x + + def clear_overhead_walltime_milliseconds(self): + if self.has_overhead_walltime_milliseconds_: + self.has_overhead_walltime_milliseconds_ = 0 + self.overhead_walltime_milliseconds_ = 0 + + def has_overhead_walltime_milliseconds(self): return self.has_overhead_walltime_milliseconds_ + + def user_email(self): return self.user_email_ + + def set_user_email(self, x): + self.has_user_email_ = 1 + self.user_email_ = x + + def clear_user_email(self): + if self.has_user_email_: + self.has_user_email_ = 0 + self.user_email_ = "" + + def has_user_email(self): return self.has_user_email_ + + def is_admin(self): return self.is_admin_ + + def set_is_admin(self, x): + self.has_is_admin_ = 1 + self.is_admin_ = x + + def clear_is_admin(self): + if self.has_is_admin_: + self.has_is_admin_ = 0 + self.is_admin_ = 0 + + def has_is_admin(self): return self.has_is_admin_ + + def individual_stats_size(self): return len(self.individual_stats_) + def individual_stats_list(self): return self.individual_stats_ + + def individual_stats(self, i): + return self.individual_stats_[i] + + def mutable_individual_stats(self, i): + return self.individual_stats_[i] + + def add_individual_stats(self): + x = IndividualRpcStatsProto() + self.individual_stats_.append(x) + return x + + def clear_individual_stats(self): + self.individual_stats_ = [] + + def MergeFrom(self, x): + assert x is not self + if (x.has_start_timestamp_milliseconds()): self.set_start_timestamp_milliseconds(x.start_timestamp_milliseconds()) + if (x.has_http_method()): self.set_http_method(x.http_method()) + if (x.has_http_path()): self.set_http_path(x.http_path()) + if (x.has_http_query()): self.set_http_query(x.http_query()) + if (x.has_http_status()): self.set_http_status(x.http_status()) + if (x.has_duration_milliseconds()): self.set_duration_milliseconds(x.duration_milliseconds()) + if (x.has_api_mcycles()): self.set_api_mcycles(x.api_mcycles()) + if (x.has_processor_mcycles()): self.set_processor_mcycles(x.processor_mcycles()) + for i in xrange(x.rpc_stats_size()): self.add_rpc_stats().CopyFrom(x.rpc_stats(i)) + for i in xrange(x.cgi_env_size()): self.add_cgi_env().CopyFrom(x.cgi_env(i)) + if (x.has_overhead_walltime_milliseconds()): self.set_overhead_walltime_milliseconds(x.overhead_walltime_milliseconds()) + if (x.has_user_email()): self.set_user_email(x.user_email()) + if (x.has_is_admin()): self.set_is_admin(x.is_admin()) + for i in xrange(x.individual_stats_size()): self.add_individual_stats().CopyFrom(x.individual_stats(i)) + + def Equals(self, x): + if x is self: return 1 + if self.has_start_timestamp_milliseconds_ != x.has_start_timestamp_milliseconds_: return 0 + if self.has_start_timestamp_milliseconds_ and self.start_timestamp_milliseconds_ != x.start_timestamp_milliseconds_: return 0 + if self.has_http_method_ != x.has_http_method_: return 0 + if self.has_http_method_ and self.http_method_ != x.http_method_: return 0 + if self.has_http_path_ != x.has_http_path_: return 0 + if self.has_http_path_ and self.http_path_ != x.http_path_: return 0 + if self.has_http_query_ != x.has_http_query_: return 0 + if self.has_http_query_ and self.http_query_ != x.http_query_: return 0 + if self.has_http_status_ != x.has_http_status_: return 0 + if self.has_http_status_ and self.http_status_ != x.http_status_: return 0 + if self.has_duration_milliseconds_ != x.has_duration_milliseconds_: return 0 + if self.has_duration_milliseconds_ and self.duration_milliseconds_ != x.duration_milliseconds_: return 0 + if self.has_api_mcycles_ != x.has_api_mcycles_: return 0 + if self.has_api_mcycles_ and self.api_mcycles_ != x.api_mcycles_: return 0 + if self.has_processor_mcycles_ != x.has_processor_mcycles_: return 0 + if self.has_processor_mcycles_ and self.processor_mcycles_ != x.processor_mcycles_: return 0 + if len(self.rpc_stats_) != len(x.rpc_stats_): return 0 + for e1, e2 in zip(self.rpc_stats_, x.rpc_stats_): + if e1 != e2: return 0 + if len(self.cgi_env_) != len(x.cgi_env_): return 0 + for e1, e2 in zip(self.cgi_env_, x.cgi_env_): + if e1 != e2: return 0 + if self.has_overhead_walltime_milliseconds_ != x.has_overhead_walltime_milliseconds_: return 0 + if self.has_overhead_walltime_milliseconds_ and self.overhead_walltime_milliseconds_ != x.overhead_walltime_milliseconds_: return 0 + if self.has_user_email_ != x.has_user_email_: return 0 + if self.has_user_email_ and self.user_email_ != x.user_email_: return 0 + if self.has_is_admin_ != x.has_is_admin_: return 0 + if self.has_is_admin_ and self.is_admin_ != x.is_admin_: return 0 + if len(self.individual_stats_) != len(x.individual_stats_): return 0 + for e1, e2 in zip(self.individual_stats_, x.individual_stats_): + if e1 != e2: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_start_timestamp_milliseconds_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: start_timestamp_milliseconds not set.') + if (not self.has_duration_milliseconds_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: duration_milliseconds not set.') + for p in self.rpc_stats_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.cgi_env_: + if not p.IsInitialized(debug_strs): initialized=0 + for p in self.individual_stats_: + if not p.IsInitialized(debug_strs): initialized=0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.start_timestamp_milliseconds_) + if (self.has_http_method_): n += 1 + self.lengthString(len(self.http_method_)) + if (self.has_http_path_): n += 1 + self.lengthString(len(self.http_path_)) + if (self.has_http_query_): n += 1 + self.lengthString(len(self.http_query_)) + if (self.has_http_status_): n += 1 + self.lengthVarInt64(self.http_status_) + n += self.lengthVarInt64(self.duration_milliseconds_) + if (self.has_api_mcycles_): n += 1 + self.lengthVarInt64(self.api_mcycles_) + if (self.has_processor_mcycles_): n += 1 + self.lengthVarInt64(self.processor_mcycles_) + n += 1 * len(self.rpc_stats_) + for i in xrange(len(self.rpc_stats_)): n += self.lengthString(self.rpc_stats_[i].ByteSize()) + n += 2 * len(self.cgi_env_) + for i in xrange(len(self.cgi_env_)): n += self.lengthString(self.cgi_env_[i].ByteSize()) + if (self.has_overhead_walltime_milliseconds_): n += 2 + self.lengthVarInt64(self.overhead_walltime_milliseconds_) + if (self.has_user_email_): n += 2 + self.lengthString(len(self.user_email_)) + if (self.has_is_admin_): n += 3 + n += 2 * len(self.individual_stats_) + for i in xrange(len(self.individual_stats_)): n += self.lengthString(self.individual_stats_[i].ByteSize()) + return n + 2 + + def Clear(self): + self.clear_start_timestamp_milliseconds() + self.clear_http_method() + self.clear_http_path() + self.clear_http_query() + self.clear_http_status() + self.clear_duration_milliseconds() + self.clear_api_mcycles() + self.clear_processor_mcycles() + self.clear_rpc_stats() + self.clear_cgi_env() + self.clear_overhead_walltime_milliseconds() + self.clear_user_email() + self.clear_is_admin() + self.clear_individual_stats() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt64(self.start_timestamp_milliseconds_) + if (self.has_http_method_): + out.putVarInt32(18) + out.putPrefixedString(self.http_method_) + if (self.has_http_path_): + out.putVarInt32(26) + out.putPrefixedString(self.http_path_) + if (self.has_http_query_): + out.putVarInt32(34) + out.putPrefixedString(self.http_query_) + if (self.has_http_status_): + out.putVarInt32(40) + out.putVarInt32(self.http_status_) + out.putVarInt32(48) + out.putVarInt64(self.duration_milliseconds_) + if (self.has_api_mcycles_): + out.putVarInt32(56) + out.putVarInt64(self.api_mcycles_) + if (self.has_processor_mcycles_): + out.putVarInt32(64) + out.putVarInt64(self.processor_mcycles_) + for i in xrange(len(self.rpc_stats_)): + out.putVarInt32(74) + out.putVarInt32(self.rpc_stats_[i].ByteSize()) + self.rpc_stats_[i].OutputUnchecked(out) + for i in xrange(len(self.cgi_env_)): + out.putVarInt32(810) + out.putVarInt32(self.cgi_env_[i].ByteSize()) + self.cgi_env_[i].OutputUnchecked(out) + if (self.has_overhead_walltime_milliseconds_): + out.putVarInt32(816) + out.putVarInt64(self.overhead_walltime_milliseconds_) + if (self.has_user_email_): + out.putVarInt32(826) + out.putPrefixedString(self.user_email_) + if (self.has_is_admin_): + out.putVarInt32(832) + out.putBoolean(self.is_admin_) + for i in xrange(len(self.individual_stats_)): + out.putVarInt32(858) + out.putVarInt32(self.individual_stats_[i].ByteSize()) + self.individual_stats_[i].OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_start_timestamp_milliseconds(d.getVarInt64()) + continue + if tt == 18: + self.set_http_method(d.getPrefixedString()) + continue + if tt == 26: + self.set_http_path(d.getPrefixedString()) + continue + if tt == 34: + self.set_http_query(d.getPrefixedString()) + continue + if tt == 40: + self.set_http_status(d.getVarInt32()) + continue + if tt == 48: + self.set_duration_milliseconds(d.getVarInt64()) + continue + if tt == 56: + self.set_api_mcycles(d.getVarInt64()) + continue + if tt == 64: + self.set_processor_mcycles(d.getVarInt64()) + continue + if tt == 74: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_rpc_stats().TryMerge(tmp) + continue + if tt == 810: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_cgi_env().TryMerge(tmp) + continue + if tt == 816: + self.set_overhead_walltime_milliseconds(d.getVarInt64()) + continue + if tt == 826: + self.set_user_email(d.getPrefixedString()) + continue + if tt == 832: + self.set_is_admin(d.getBoolean()) + continue + if tt == 858: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.add_individual_stats().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_start_timestamp_milliseconds_: res+=prefix+("start_timestamp_milliseconds: %s\n" % self.DebugFormatInt64(self.start_timestamp_milliseconds_)) + if self.has_http_method_: res+=prefix+("http_method: %s\n" % self.DebugFormatString(self.http_method_)) + if self.has_http_path_: res+=prefix+("http_path: %s\n" % self.DebugFormatString(self.http_path_)) + if self.has_http_query_: res+=prefix+("http_query: %s\n" % self.DebugFormatString(self.http_query_)) + if self.has_http_status_: res+=prefix+("http_status: %s\n" % self.DebugFormatInt32(self.http_status_)) + if self.has_duration_milliseconds_: res+=prefix+("duration_milliseconds: %s\n" % self.DebugFormatInt64(self.duration_milliseconds_)) + if self.has_api_mcycles_: res+=prefix+("api_mcycles: %s\n" % self.DebugFormatInt64(self.api_mcycles_)) + if self.has_processor_mcycles_: res+=prefix+("processor_mcycles: %s\n" % self.DebugFormatInt64(self.processor_mcycles_)) + cnt=0 + for e in self.rpc_stats_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("rpc_stats%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + cnt=0 + for e in self.cgi_env_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("cgi_env%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + if self.has_overhead_walltime_milliseconds_: res+=prefix+("overhead_walltime_milliseconds: %s\n" % self.DebugFormatInt64(self.overhead_walltime_milliseconds_)) + if self.has_user_email_: res+=prefix+("user_email: %s\n" % self.DebugFormatString(self.user_email_)) + if self.has_is_admin_: res+=prefix+("is_admin: %s\n" % self.DebugFormatBool(self.is_admin_)) + cnt=0 + for e in self.individual_stats_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("individual_stats%s <\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + cnt+=1 + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kstart_timestamp_milliseconds = 1 + khttp_method = 2 + khttp_path = 3 + khttp_query = 4 + khttp_status = 5 + kduration_milliseconds = 6 + kapi_mcycles = 7 + kprocessor_mcycles = 8 + krpc_stats = 9 + kcgi_env = 101 + koverhead_walltime_milliseconds = 102 + kuser_email = 103 + kis_admin = 104 + kindividual_stats = 107 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "start_timestamp_milliseconds", + 2: "http_method", + 3: "http_path", + 4: "http_query", + 5: "http_status", + 6: "duration_milliseconds", + 7: "api_mcycles", + 8: "processor_mcycles", + 9: "rpc_stats", + 101: "cgi_env", + 102: "overhead_walltime_milliseconds", + 103: "user_email", + 104: "is_admin", + 107: "individual_stats", + }, 107) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.NUMERIC, + 6: ProtocolBuffer.Encoder.NUMERIC, + 7: ProtocolBuffer.Encoder.NUMERIC, + 8: ProtocolBuffer.Encoder.NUMERIC, + 9: ProtocolBuffer.Encoder.STRING, + 101: ProtocolBuffer.Encoder.STRING, + 102: ProtocolBuffer.Encoder.NUMERIC, + 103: ProtocolBuffer.Encoder.STRING, + 104: ProtocolBuffer.Encoder.NUMERIC, + 107: ProtocolBuffer.Encoder.STRING, + }, 107, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['AggregateRpcStatsProto','KeyValProto','StackFrameProto','IndividualRpcStatsProto','RequestStatProto'] diff --git a/google_appengine/google/appengine/ext/appstats/formatting.py b/google_appengine/google/appengine/ext/appstats/formatting.py new file mode 100755 index 0000000..fc92c96 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/formatting.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A fast but lossy, totally generic object formatter.""" + + +import types + + +EASY_TYPES = (type(None), int, long, float, bool) +META_TYPES = (type, types.ClassType) +STRING_TYPES = (str, unicode) +CONTAINER_TYPES = {tuple: ('(', ')'), + list: ('[', ']'), + dict: ('{', '}'), + } +BUILTIN_TYPES = EASY_TYPES + STRING_TYPES + tuple(CONTAINER_TYPES) +INSTANCE_TYPE = types.InstanceType + + +def format_value(val, limit=100, level=10): + """Wrapper around _format_value().""" + return _format_value(val, limit, level) + + +def _format_value(val, limit, level, len=len, repr=repr): + """Format an arbitrary value as a compact string. + + This is a variant on Python's built-in repr() function, also + borrowing some ideas from the repr.py standard library module, but + tuned for speed even in extreme cases (like very large longs or very + long strings) and safety (it never invokes user code). + + For structured data types like lists and objects it calls itself + recursively; recursion is strictly limited by level. + + Python's basic types (numbers, strings, lists, tuples, dicts, bool, + and None) are represented using their familiar Python notations. + Objects are represented as ClassName. + Portions omitted due to the various limits are represented using + three dots ('...'). + + Args: + val: An arbitrary value. + limit: Limit on the output length. + level: Recursion level countdown. + len, repr: Not arguments; for optimization. + + Returns: + A str instance. + """ + if level <= 0: + return '...' + + typ = type(val) + + if typ in EASY_TYPES: + if typ is float: + rep = str(val) + elif typ is long: + if val >= 10L**99: + return '...L' + elif val <= -10L**98: + return '-...L' + else: + rep = repr(val) + else: + rep = repr(val) + if typ is long and len(rep) > limit: + n1 = (limit - 3) // 2 + n2 = (limit - 3) - n1 + rep = rep[:n1] + '...' + rep[-n2:] + return rep + + if typ in META_TYPES: + return val.__name__ + + if typ in STRING_TYPES: + n1 = (limit - 3) // 2 + n2 = (limit - 3) - n1 + if len(val) > limit: + head = repr(val[:n1]) + tail = repr(val[-n2:]) + return head[:n1] + '...' + tail[-n2:] + rep = repr(val) + if len(rep) <= limit: + return rep + return rep[:n1] + '...' + rep[-n2:] + + if typ is types.MethodType: + if val.im_self is None: + fmt = '' + else: + fmt = '>' + return fmt % (val.__name__, val.im_class.__name__) + + if typ is types.FunctionType: + nam = val.__name__ + if nam == '': + return nam + else: + return '' % val.__name__ + + if typ is types.BuiltinFunctionType: + if val.__self__ is not None: + return '>' % (val.__name__, + val.__self__.__class__.__name__) + else: + return '' % val.__name__ + + if typ is types.ModuleType: + if hasattr(val, '__file__'): + return '' % val.__name__ + else: + return '' % val.__name__ + + if typ is types.CodeType: + return '' % val.co_name + + dct = getattr(val, '__dict__', None) + if type(dct) is dict: + if typ is INSTANCE_TYPE: + typ = val.__class__ + typnam = typ.__name__ + priv = '_' + typnam + '__' + buffer = [typnam, '<'] + limit -= len(buffer[0]) + 2 + if len(dct) <= limit//4: + names = sorted(dct) + else: + names = list(dct) + append = buffer.append + first = True + + if issubclass(typ, BUILTIN_TYPES): + for builtin_typ in BUILTIN_TYPES: + if issubclass(typ, builtin_typ): + try: + val = builtin_typ(val) + assert type(val) is builtin_typ + except Exception: + break + else: + append(_format_value(val, limit, level-1)) + first = False + break + + for nam in names: + if not isinstance(nam, basestring): + continue + if first: + first = False + else: + append(', ') + pnam = nam + if pnam.startswith(priv): + pnam = pnam[len(priv)-2:] + limit -= len(pnam) + 2 + if limit <= 0: + append('...') + break + append(pnam) + append('=') + rep = _format_value(dct[nam], limit, level-1) + limit -= len(rep) + append(rep) + append('>') + return ''.join(buffer) + + how = CONTAINER_TYPES.get(typ) + if how: + head, tail = how + buffer = [head] + append = buffer.append + limit -= 2 + series = val + isdict = typ is dict + if isdict and len(val) <= limit//4: + series = sorted(val) + for elem in series: + if limit <= 0: + append('...') + break + rep = _format_value(elem, limit, level-1) + limit -= len(rep) + 2 + append(rep) + if isdict: + rep = _format_value(val[elem], limit, level-1) + limit -= len(rep) + append(':') + append(rep) + append(', ') + if buffer[-1] == ', ': + if tail == ')' and len(val) == 1: + buffer[-1] = ',)' + else: + buffer[-1] = tail + else: + append(tail) + return ''.join(buffer) + + if issubclass(typ, BUILTIN_TYPES): + for builtin_typ in BUILTIN_TYPES: + if issubclass(typ, builtin_typ): + try: + val = builtin_typ(val) + assert type(val) is builtin_typ + except Exception: + break + else: + typnam = typ.__name__ + limit -= len(typnam) + 2 + return '%s<%s>' % (typnam, _format_value(val, limit, level-1)) + + return typ.__name__ + '<>' diff --git a/google_appengine/google/appengine/ext/appstats/recording.py b/google_appengine/google/appengine/ext/appstats/recording.py new file mode 100755 index 0000000..3257347 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/recording.py @@ -0,0 +1,971 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Userland RPC instrumentation for App Engine.""" + + +import datetime +import logging +import os +import random +import re +import sys +import time + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import lib_config +from google.appengine.api import memcache +from google.appengine.api import quota +from google.appengine.api import users + +from google.appengine.ext.appstats import datamodel_pb +from google.appengine.ext.appstats import formatting + + +class ConfigDefaults(object): + """Configurable constants. + + To override appstats configuration valuess, define values like this + in your appengine_config.py file (in the root of your app): + + appstats_MAX_STACK = 5 + appstats_MAX_LOCALS = 0 + + More complete documentation for all configurable constants can be + found in the file sample_appengine_config.py. + """ + + DEBUG = False + DUMP_LEVEL = -1 + + KEY_DISTANCE = 100 + KEY_MODULUS = 1000 + + KEY_NAMESPACE = '__appstats__' + KEY_PREFIX = '__appstats__' + KEY_TEMPLATE = ':%06d' + PART_SUFFIX = ':part' + FULL_SUFFIX = ':full' + LOCK_SUFFIX = '' + + MAX_STACK = 10 + MAX_LOCALS = 10 + MAX_REPR = 100 + MAX_DEPTH = 10 + + RE_STACK_BOTTOM = r'dev_appserver\.py' + RE_STACK_SKIP = r'recording\.py|apiproxy_stub_map\.py' + + LOCK_TIMEOUT = 1 + + TZOFFSET = 8*3600 + + stats_url = '/stats' + + RECORD_FRACTION = 1.0 + + FILTER_LIST = [] + + + def should_record(env): + """Return a bool indicating whether we should record this request. + + Args: + env: The CGI or WSGI environment dict. + + Returns: + True if this request should be recorded, False if not. + + The default implementation returns True iff the request matches + FILTER_LIST (see above) *and* random.random() < RECORD_FRACTION. + """ + if config.FILTER_LIST: + if config.DEBUG: + logging.debug('FILTER_LIST: %r', config.FILTER_LIST) + for filter_dict in config.FILTER_LIST: + for key, regex in filter_dict.iteritems(): + negated = isinstance(regex, str) and regex.startswith('!') + if negated: + regex = regex[1:] + value = env.get(key, '') + if bool(re.match(regex, value)) == negated: + if config.DEBUG: + logging.debug('No match on %r for %s=%r', regex, key, value) + break + else: + if config.DEBUG: + logging.debug('Match on %r', filter_dict) + break + else: + if config.DEBUG: + logging.debug('Non-empty FILTER_LIST, but no filter matches') + return False + if config.RECORD_FRACTION >= 1.0: + return True + return random.random() < config.RECORD_FRACTION + + def normalize_path(path): + """Transform a path to a canonical key for that path. + + Args: + path: A string, e.g. '/foo/bar/12345'. + + Returns: + A string derived from path, e.g. '/foo/bar/X'. + """ + return path + + def extract_key(request): + """Extract a canonical key from a StatsProto instance. + + This default implementation calls config.normalize_path() on the + path returned by request.http_path(), and then prepends the HTTP + method and a space, unless the method is 'GET', in which case the + method and the space are omitted (so as to display a more compact + key in the user interface). + + Args: + request: a StatsProto instance. + + Returns: + A string, typically something like '/foo/bar/X' or 'POST /foo/bar'. + """ + key = config.normalize_path(request.http_path()) + if request.http_method() != 'GET': + key = '%s %s' % (request.http_method(), key) + return key + + +config = lib_config.register('appstats', ConfigDefaults.__dict__) + + +class Recorder(object): + """In-memory state for the current request. + + An instance is created soon after the request is received, and + stored in the global variable 'recorder'. It collects information + about the request and about individual RPCs made during the request, + until just before the response is sent out, when the recorded + information is saved to memcache by calling the save() method. + """ + + def __init__(self, env): + """Constructor. + + Args: + env: A dict giving the CGI or WSGI environment. + """ + self.env = dict(kv for kv in env.iteritems() if isinstance(kv[1], str)) + self.start_timestamp = time.time() + self.http_status = 0 + self.end_timestamp = self.start_timestamp + self.traces = [] + self.pending = {} + self.overhead = (time.time() - self.start_timestamp) + + + def http_method(self): + """Return the request method, e.g. 'GET' or 'POST'.""" + return self.env.get('REQUEST_METHOD', 'GET') + + def http_path(self): + """Return the request path, e.g. '/' or '/foo/bar', excluding the query.""" + return self.env.get('PATH_INFO', '') + + def http_query(self): + """Return the query string, if any, with '?' prefix. + + If there is no query string, an empty string is returned (i.e. not '?'). + """ + query_string = self.env.get('QUERY_STRING', '') + if query_string: + query_string = '?' + query_string + return query_string + + def record_custom_event(self, label, data=None): + """Record a custom event. + + Args: + label: A string to use as event label; a 'custom.' prefix will be added. + data: Optional value to record. This can be anything; the value + will be formatted using format_value() before it is recorded. + """ + pre_now = time.time() + sreq = format_value(data) + now = time.time() + delta = int(1000 * (now - self.start_timestamp)) + trace = datamodel_pb.IndividualRpcStatsProto() + self.get_call_stack(trace) + trace.set_service_call_name('custom.' + label) + trace.set_request_data_summary(sreq) + trace.set_start_offset_milliseconds(delta) + self.traces.append(trace) + self.overhead += (now - pre_now) + + def record_rpc_request(self, service, call, request, response, rpc): + """Record the request of an RPC call. + + Args: + service: The service name, e.g. 'memcache'. + call: The call name, e.g. 'Get'. + request: The request object. + response: The response object (ignored). + rpc: The RPC object; may be None. + """ + pre_now = time.time() + sreq = format_value(request) + if rpc is not None: + self.pending[rpc] = len(self.traces) + now = time.time() + delta = int(1000 * (now - self.start_timestamp)) + trace = datamodel_pb.IndividualRpcStatsProto() + self.get_call_stack(trace) + trace.set_service_call_name('%s.%s' % (service, call)) + trace.set_request_data_summary(sreq) + trace.set_start_offset_milliseconds(delta) + self.traces.append(trace) + self.overhead += (now - pre_now) + + def record_rpc_response(self, service, call, request, response, rpc): + """Record the response of an RPC call. + + Args: + service: The service name, e.g. 'memcache'. + call: The call name, e.g. 'Get'. + request: The request object. + response: The response object (ignored). + rpc: The RPC object; may be None. + + This first tries to match the request with an unmatched request trace. + If no matching request trace is found, this is logged as a new trace. + """ + now = time.time() + key = '%s.%s' % (service, call) + delta = int(1000 * (now - self.start_timestamp)) + sresp = format_value(response) + api_mcycles = 0 + if rpc is not None: + api_mcycles = rpc.cpu_usage_mcycles + index = self.pending.get(rpc) + if index is not None: + del self.pending[rpc] + if 0 <= index < len(self.traces): + trace = self.traces[index] + trace.set_response_data_summary(sresp) + trace.set_api_mcycles(api_mcycles) + duration = delta - trace.start_offset_milliseconds() + trace.set_duration_milliseconds(duration) + self.overhead += (time.time() - now) + return + else: + for trace in reversed(self.traces): + if (trace.service_call_name() == key and + not trace.response_data_summary()): + if config.DEBUG: + logging.debug('Matched RPC response without rpc object') + trace.set_response_data_summary(sresp) + duration = delta - trace.start_offset_milliseconds() + trace.set_duration_milliseconds(duration) + self.overhead += (time.time() - now) + return + + logging.warn('RPC response without matching request') + trace = datamodel_pb.IndividualRpcStatsProto() + self.get_call_stack(trace) + trace.set_service_call_name(key) + trace.set_request_data_summary(sresp) + trace.set_start_offset_milliseconds(delta) + self.traces.append(trace) + self.overhead += (time.time() - now) + + def record_http_status(self, status): + """Record the HTTP status code and the end time of the HTTP request.""" + try: + self.http_status = int(status) + except (ValueError, TypeError): + self.http_status = 0 + self.end_timestamp = time.time() + + def save(self): + """Save the recorded data to memcache and log some info. + + This wraps the _save() method, which does the actual work; this + function just logs the total time it took and some other statistics. + """ + t0 = time.time() + if self.pending: + logging.warn('Found %d RPC request(s) without matching response ' + '(presumably due to timeouts or other errors)', + len(self.pending)) + self.dump() + try: + key, len_part, len_full = self._save() + except Exception: + logging.exception('Recorder.save() failed') + return + t1 = time.time() + link = 'http://%s%s/details?time=%s' % ( + self.env.get('HTTP_HOST', ''), + config.stats_url, + int(self.start_timestamp * 1000)) + logging.info('Saved; key: %s, part: %s bytes, full: %s bytes, ' + 'overhead: %.3f + %.3f; link: %s', + key, len_part, len_full, self.overhead, t1-t0, link) + + def _save(self): + """Internal function to save the recorded data to memcache. + + Returns: + A tuple (key, summary_size, full_size). + """ + part, full = self.get_both_protos_encoded() + key = make_key(self.start_timestamp) + errors = memcache.set_multi({config.PART_SUFFIX: part, + config.FULL_SUFFIX: full}, + time=36*3600, key_prefix=key, + namespace=config.KEY_NAMESPACE) + if errors: + logging.warn('Memcache set_multi() error: %s', errors) + return key, len(part), len(full) + + def get_both_protos_encoded(self): + """Return a string representing all recorded info an encoded protobuf. + + This calls self.get_full_proto() and calls the .Encode() method of + the resulting object; if the resulting string is too large, it + tries a number of increasingly aggressive strategies for chopping + the data down. + """ + proto = self.get_summary_proto() + part_encoded = proto.Encode() + self.add_full_info_to_proto(proto) + full_encoded = proto.Encode() + if len(full_encoded) <= memcache.MAX_VALUE_SIZE: + return part_encoded, full_encoded + if config.MAX_LOCALS > 0: + for trace in proto.individual_stats_list(): + for frame in trace.call_stack_list(): + frame.clear_variables() + full_encoded = proto.Encode() + if len(full_encoded) <= memcache.MAX_VALUE_SIZE: + logging.warn('Full proto too large to save, cleared variables.') + return part_encoded, full_encoded + if config.MAX_STACK > 0: + for trace in proto.individual_stats_list(): + trace.clear_call_stack() + full_encoded = proto.Encode() + if len(full_encoded) <= memcache.MAX_VALUE_SIZE: + logging.warn('Full proto way too large to save, cleared frames.') + return part_encoded, full_encoded + logging.warn('Full proto WAY too large to save, clipped to 100 traces.') + del proto.individual_stats_list()[100:] + full_encoded = proto.Encode() + return part_encoded, full_encoded + + def add_full_info_to_proto(self, proto): + """Update a protobuf representing with additional data.""" + user_email = self.env.get('USER_EMAIL') + if user_email: + proto.set_user_email(user_email) + if self.env.get('USER_IS_ADMIN') == '1': + proto.set_is_admin(True) + for key, value in sorted(self.env.iteritems()): + x = proto.add_cgi_env() + x.set_key(key) + x.set_value(value) + proto.individual_stats_list()[:] = self.traces + + def json(self): + """Return a JSON-ifyable representation of the pertinent data. + + This is for FirePython/FireLogger so we must limit the volume by + omitting stack traces and environment. Also, times and megacycles + are converted to integers representing milliseconds. + """ + traces = [] + for t in self.traces: + d = {'start': t.start_offset_milliseconds(), + 'call': t.service_call_name(), + 'request': t.request_data_summary(), + 'response': t.response_data_summary(), + 'duration': t.duration_milliseconds(), + 'api': mcycles_to_msecs(t.api_mcycles()), + } + traces.append(d) + return { + 'start': int(self.start_timestamp * 1000), + 'duration': int((self.end_timestamp - self.start_timestamp) * 1000), + 'cpu': mcycles_to_msecs(quota.get_request_cpu_usage()), + 'overhead': int(self.overhead * 1000), + 'traces': traces, + } + + def get_summary_proto_encoded(self): + """Return a string representing a summary an encoded protobuf. + + This calls self.get_summary_proto() and calls the .Encode() + method of the resulting object. + """ + return self.get_summary_proto().Encode() + + def get_summary_proto(self): + """Return a protobuf representing a summary of this recorder.""" + summary = datamodel_pb.RequestStatProto() + summary.set_start_timestamp_milliseconds(int(self.start_timestamp * 1000)) + method = self.http_method() + if method != 'GET': + summary.set_http_method(method) + path = self.http_path() + if path != '/': + summary.set_http_path(path) + query = self.http_query() + if query: + summary.set_http_query(query) + status = int(self.http_status) + if status != 200: + summary.set_http_status(status) + duration = int(1000 * (self.end_timestamp - self.start_timestamp)) + summary.set_duration_milliseconds(duration) + api_mcycles = self.get_total_api_mcycles() + if api_mcycles: + summary.set_api_mcycles(api_mcycles) + summary.set_processor_mcycles(quota.get_request_cpu_usage()) + summary.set_overhead_walltime_milliseconds(int(self.overhead * 1000)) + rpc_stats = self.get_rpcstats().items() + rpc_stats.sort(key=lambda x: (-x[1], x[0])) + for key, value in rpc_stats: + x = summary.add_rpc_stats() + x.set_service_call_name(key) + x.set_total_amount_of_calls(value) + return summary + + def get_rpcstats(self): + """Compute RPC statistics (how often each RPC endpoint is called). + + Returns: + A dict mapping 'service.call' keys to integers giving call counts. + """ + rpcstats = {} + for trace in self.traces: + key = trace.service_call_name() + if key in rpcstats: + rpcstats[key] += 1 + else: + rpcstats[key] = 1 + return rpcstats + + def get_total_api_mcycles(self): + """Compute the total amount of API time for all RPCs. + + Returns: + An integer expressing megacycles. + """ + mcycles = 0 + for trace in self.traces: + trace_mc = trace.api_mcycles() + if isinstance(trace_mc, int): + mcycles += trace_mc + return mcycles + + def dump(self, level=None): + """Log the recorded data, for debugging. + + This logs messages using logging.info(). The amount of data + logged is controlled by the level argument, which defaults to + config.DUMP_LEVEL; if < 0 (the default) nothing is logged. + """ + if level is None: + level = config.DUMP_LEVEL + if level < 0: + return + logging.info('APPSTATS: %s "%s %s%s" %s %.3f', + format_time(self.start_timestamp), + self.http_method(), + self.http_path(), + self.http_query(), + self.http_status, + self.end_timestamp - self.start_timestamp) + for key, value in sorted(self.get_rpcstats().iteritems()): + logging.info(' %s : %s', key, value) + if level <= 0: + return + for trace in self.traces: + start = trace.start_offset_milliseconds() + logging.info(' TRACE : [%s, %s, %s, %s]', + trace.start_offset_milliseconds(), + trace.service_call_name(), + trace.duration_milliseconds(), + trace.api_mcycles()) + logging.info(' REQ : %s', trace.request_data_summary()) + logging.info(' RESP : %s', trace.response_data_summary()) + if level <= 1: + continue + for entry in trace.call_stack_list(): + logging.info(' FRAME: %s:%s %s()', + entry.class_or_file_name(), + entry.line_number(), + entry.function_name()) + for variable in entry.variables_list(): + logging.info(' VAR: %s = %s', variable.key(), variable.value()) + + def get_call_stack(self, trace): + """Extract the current call stack. + + The stack is limited to at most config.MAX_STACK frames; frames + recognized by config.RE_STACK_SKIP are skipped; a frame recognized + by config.RE_STACK_BOTTOM terminates the stack search. + + Args: + trace: An IndividualRpcStatsProto instance that will be updated. + """ + frame = sys._getframe(0) + while frame is not None and trace.call_stack_size() < config.MAX_STACK: + if not self.get_frame_summary(frame, trace): + break + frame = frame.f_back + + sys_path_entries = None + + @classmethod + def init_sys_path_entries(cls): + """Initialize the class variable path_entries. + + The variable will hold a list of (i, entry) tuples where + entry == sys.path[i], sorted from shortest to longest entry. + """ + cls.sys_path_entries = sorted(enumerate(sys.path), + key=lambda x: (-len(x[1]), x[0])) + + def get_frame_summary(self, frame, trace): + """Return a frame summary. + + Args: + frame: A Python stack frame object. + trace: An IndividualRpcStatsProto instance that will be updated. + + Returns: + False if this stack frame matches config.RE_STACK_BOTTOM. + True otherwise. + """ + if self.sys_path_entries is None: + self.init_sys_path_entries() + filename = frame.f_code.co_filename + if filename and not (filename.startswith('<') and filename.endswith('>')): + for i, entry in self.sys_path_entries: + if filename.startswith(entry): + filename = '' % i + filename[len(entry):] + break + else: + logging.info('No prefix for %s', filename) + funcname = frame.f_code.co_name + lineno = frame.f_lineno + + code_key = '%s:%s:%s' % (filename, funcname, lineno) + if re.search(config.RE_STACK_BOTTOM, code_key): + return False + if re.search(config.RE_STACK_SKIP, code_key): + return True + entry = trace.add_call_stack() + entry.set_class_or_file_name(filename) + entry.set_line_number(lineno) + entry.set_function_name(funcname) + if frame.f_globals is frame.f_locals: + return True + + max_locals = config.MAX_LOCALS + if max_locals <= 0: + return True + + for name, value in sorted(frame.f_locals.iteritems()): + x = entry.add_variables() + x.set_key(name) + x.set_value(format_value(value)) + max_locals -= 1 + if max_locals <= 0: + break + + return True + + +def mcycles_to_seconds(mcycles): + """Helper function to convert megacycles to seconds.""" + if mcycles is None: + return 0 + return quota.megacycles_to_cpu_seconds(mcycles) + + +def mcycles_to_msecs(mcycles): + """Helper function to convert megacycles to milliseconds.""" + return int(mcycles_to_seconds(mcycles) * 1000) + + +def make_key(timestamp): + """Return the key (less suffix) to which a timestamp maps. + + Args: + timestamp: A timestamp, expressed using the standard Python + convention for timestamps (a float giving seconds and fractional + seconds since the POSIX timestamp epoch). + + Returns: + A string, formed by concatenating config.KEY_PREFIX and + config.KEY_TEMPLATE with some of the lower digits of the timestamp + converted to milliseconds substituted in the template (which should + contain exactly one %-format like '%d'). + """ + distance = config.KEY_DISTANCE + modulus = config.KEY_MODULUS + tmpl = config.KEY_PREFIX + config.KEY_TEMPLATE + msecs = int(timestamp * 1000) + index = ((msecs // distance) % modulus) * distance + return tmpl % index + + +def format_time(timestamp): + """Utility to format a timestamp in UTC. + + Args: + timestamp: A float representing a standard Python time (see make_key()). + """ + timestamp = datetime.datetime.utcfromtimestamp(timestamp) + timestamp -= datetime.timedelta(seconds=config.TZOFFSET) + return timestamp.isoformat()[:-3].replace('T', ' ') + + +def format_value(val): + """Format an arbitrary value as a compact string. + + This wraps formatting._format_value() passing it our config variables. + """ + return formatting._format_value(val, config.MAX_REPR, config.MAX_DEPTH) + + +class StatsProto(datamodel_pb.RequestStatProto): + """A subclass if RequestStatProto with a number of extra attributes. + + This exists mainly so that ui.py can pass an instance of this class + directly to a Django template, and hive the Django template access + to formatted times and megacycles converted to milliseconds without + using custom tags. (Though arguably the latter would be more + convenient for the Java version of Appstats.) + + This adds the following methods: + + - .start_time_formatted(): .start_time_milliseconds() nicely formatted. + - .api_milliseconds(): .api_mcycles() converted to milliseconds. + - .processor_milliseconds(): .processor_mcycles() converted to milliseconds. + - .combined_rpc_count(): total number of RPCs, computed from + .rpc_stats_list(). (This is cached as .__combined_rpc_count.) + + All these are methods to remain close in style to the protobuffer + access methods. + + In addition, each of the entries in .individual_stats_list() is given + a .api_milliseconds attribute (not a method, since we cannot subclass + the class used for these entries easily, but we can add attributes + to the instances in our constructor). + """ + + def __init__(self, *args, **kwds): + """Constructor. + + This exists solely so it can pre-populate the .api_milliseconds + attributes of the entries in .individual_stats_list(). + """ + datamodel_pb.RequestStatProto.__init__(self, *args, **kwds) + for r in self.individual_stats_list(): + r.api_milliseconds = mcycles_to_msecs(r.api_mcycles()) + + def start_time_formatted(self): + """Return a string representing .start_timestamp_milliseconds().""" + return format_time(self.start_timestamp_milliseconds() * 0.001) + + def api_milliseconds(self): + """Return an int giving .api_mcycles() converted to milliseconds.""" + return mcycles_to_msecs(self.api_mcycles()) + + def processor_milliseconds(self): + """Return an int giving .processor_mcycles() converted to milliseconds.""" + return mcycles_to_msecs(self.processor_mcycles()) + + __combined_rpc_count = None + + def combined_rpc_count(self): + """Return the total number of RPCs across .rpc_stats_list().""" + if self.__combined_rpc_count is None: + self.__combined_rpc_count = sum(x.total_amount_of_calls() + for x in self.rpc_stats_list()) + return self.__combined_rpc_count + + +def load_summary_protos(): + """Load all valid summary records from memcache. + + Returns: + A list of StatsProto instances, in reverse chronological order + (i.e. most recent first). + + NOTE: This is limited to returning at most config.KEY_MODULUS records, + since there are only that many distinct keys. See also make_key(). + """ + tmpl = config.KEY_PREFIX + config.KEY_TEMPLATE + config.PART_SUFFIX + keys = [tmpl % i + for i in + range(0, config.KEY_DISTANCE * config.KEY_MODULUS, + config.KEY_DISTANCE)] + results = memcache.get_multi(keys, namespace=config.KEY_NAMESPACE) + records = [] + for rec in results.itervalues(): + try: + pb = StatsProto(rec) + except Exception, err: + logging.warn('Bad record: %s', err) + else: + records.append(pb) + logging.info('Loaded %d raw records, %d valid', len(results), len(records)) + records.sort(key=lambda pb: -pb.start_timestamp_milliseconds()) + return records + + +def load_full_proto(timestamp): + """Load the full record for a given timestamp. + + Args: + timestamp: The start_timestamp of the record, as a float in seconds + (see make_key() for details). + + Returns: + A StatsProto instance if the record exists and can be loaded; + None otherwise. + """ + full_key = make_key(timestamp) + config.FULL_SUFFIX + full_binary = memcache.get(full_key, namespace=config.KEY_NAMESPACE) + if full_binary is None: + logging.info('No full record at %s', full_key) + return None + try: + full = StatsProto(full_binary) + except Exception, err: + logging.warn('Bad full record at %s: %s', full_key, err) + return None + if full.start_timestamp_milliseconds() != int(timestamp * 1000): + logging.warn('Hash collision, record at %d has timestamp %d', + int(timestamp * 1000), full.start_timestamp_milliseconds()) + return None + return full + + +class AppstatsDjangoMiddleware(object): + """Django Middleware to install the instrumentation. + + To start recording your app's RPC statistics, add + + 'google.appengine.ext.appstats.recording.AppstatsDjangoMiddleware', + + to the MIDDLEWARE_CLASSES entry in your Django settings.py file. + It's best to insert it in front of any other middleware classes, + since some other middleware may make RPC calls and those won't be + recorded if that middleware is invoked before this middleware. + + See http://docs.djangoproject.com/en/dev/topics/http/middleware/. + + Special note for FirePython users: when combining FirePython and + Appstats through Django middleware, place the FirePython middleware + first. IOW FirePython must wrap Appstats, not the other way around. + """ + + def process_request(self, request): + """Called by Django before deciding which view to execute.""" + start_recording() + + def process_response(self, request, response): + """Called by Django just before returning a response.""" + firepython_set_extension_data = getattr( + request, + 'firepython_set_extension_data', + None) + end_recording(response.status_code, firepython_set_extension_data) + return response + + +AppStatsDjangoMiddleware = AppstatsDjangoMiddleware + + +def appstats_wsgi_middleware(app): + """WSGI Middleware to install the instrumentation. + + Normally you specify this middleware in your appengine_config.py + file, like this: + + def webapp_add_wsgi_middleware(app): + from google.appengine.ext.appstats import recording + app = recording.appstats_wsgi_middleware(app) + return app + + See Python PEP 333, http://www.python.org/dev/peps/pep-0333/ for + more information about the WSGI standard. + """ + + def appstats_wsgi_wrapper(environ, start_response): + """Outer wrapper function around the WSGI protocol. + + The top-level appstats_wsgi_middleware() function returns this + function to the caller instead of the app class or function passed + in. When the caller calls this function (which may happen + multiple times, to handle multiple requests) this function + instantiates the app class (or calls the app function), sandwiched + between calls to start_recording() and end_recording() which + manipulate the recording state. + + The signature is determined by the WSGI protocol. + """ + start_recording(environ) + save_status = [None] + + firepython_set_extension_data = environ.get('firepython.set_extension_data') + + def appstats_start_response(status, headers, exc_info=None): + """Inner wrapper function for the start_response() function argument. + + The purpose of this wrapper is save the HTTP status (which the + WSGI protocol only makes available through the start_response() + function) into the surrounding scope. This is done through a + hack because Python 2.x doesn't support assignment to nonlocal + variables. If this function is called more than once, the last + status value will be used. + + The signature is determined by the WSGI protocol. + """ + save_status.append(status) + return start_response(status, headers, exc_info) + + try: + result = app(environ, appstats_start_response) + except Exception: + end_recording(500, firepython_set_extension_data) + raise + if result is not None: + for value in result: + yield value + status = save_status[-1] + if status is not None: + status = status[:3] + end_recording(status, firepython_set_extension_data) + + return appstats_wsgi_wrapper + + +recorder = None + + +def dont_record(): + """API to prevent recording of the current request. Used by ui.py.""" + global recorder + recorder = None + + +def lock_key(): + """Return the key name to use for the memcache lock.""" + return config.KEY_PREFIX + config.LOCK_SUFFIX + + +def start_recording(env=None): + """Start recording RPC traces. + + This creates a Recorder instance and stores it in the global + variable 'recorder'. + + Args: + env: Optional WSGI environment; defaults to os.environ. + """ + global recorder + recorder = None + if env is None: + env = os.environ + if not config.should_record(env): + return + if memcache.add(lock_key(), 0, + time=config.LOCK_TIMEOUT, namespace=config.KEY_NAMESPACE): + recorder = Recorder(env) + if config.DEBUG: + logging.debug('Set recorder') + + +def end_recording(status, firepython_set_extension_data=None): + """Stop recording RPC traces and save all traces to memcache. + + This resets the global 'recorder' variable to None. + + Args: + status: HTTP Status, a 3-digit integer. + firepython_set_extension_data: Optional function to be called + to pass the recorded data to FirePython. + """ + global recorder + rec = recorder + recorder = None + if config.DEBUG: + logging.debug('Cleared recorder') + if rec is not None: + try: + rec.record_http_status(status) + rec.save() + if (firepython_set_extension_data and + (os.getenv('SERVER_SOFTWARE', '').startswith('Dev') or + users.is_current_user_admin())): + logging.info('Passing data to firepython') + firepython_set_extension_data('appengine_appstats', rec.json()) + finally: + memcache.delete(lock_key(), namespace=config.KEY_NAMESPACE) + + +def pre_call_hook(service, call, request, response, rpc=None): + """Pre-Call hook function for apiprixy_stub_map. + + The signature is determined by the CallHooks protocol. In certain + cases, rpc may be omitted. + + Once registered, this fuction will be called right before any kind + of RPC call is made through apiproxy_stub_map. The arguments are + passed on to the record_rpc_request() method of the global + 'recorder' variable, unless the latter is None. + """ + if recorder is not None: + if config.DEBUG: + logging.debug('pre_call_hook: recording %s.%s', service, call) + recorder.record_rpc_request(service, call, request, response, rpc) + + +def post_call_hook(service, call, request, response, rpc=None, error=None): + """Post-Call hook function for apiproxy_stub_map. + + The signature is determined by the CallHooks protocol. In certain + cases, rpc and/or error may be omitted. + + Once registered, this fuction will be called right after any kind of + RPC call made through apiproxy_stub_map returns. The call is passed + on to the record_rpc_request() method of the global 'recorder' + variable, unless the latter is None. + """ + if recorder is not None: + if config.DEBUG: + logging.debug('post_call_hook: recording %s.%s', service, call) + recorder.record_rpc_response(service, call, request, response, rpc) + + +apiproxy_stub_map.apiproxy.GetPreCallHooks().Append('appstats', pre_call_hook) +apiproxy_stub_map.apiproxy.GetPostCallHooks().Append('appstats', post_call_hook) diff --git a/google_appengine/google/appengine/ext/appstats/sample_appengine_config.py b/google_appengine/google/appengine/ext/appstats/sample_appengine_config.py new file mode 100755 index 0000000..3acfd85 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/sample_appengine_config.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Sample Appstats Configuration. + +There are four sections: + +0) WSGI middleware declaration. +1) Django version declaration. +2) Configuration constants. +3) Configuration functions. + +""" + + +import logging +import random +import re + +# 0) WSGI middleware declaration. + +# Only use this if you're not Django; with Django, it's easier to add +# 'google.appengine.ext.appstats.recording.AppstatsDjangoMiddleware', +# to your Django settings.py file. + +# # def webapp_add_wsgi_middleware(app): +# # from google.appengine.ext.appstats import recording +# # app = recording.appstats_wsgi_middleware(app) +# # return app + + +# 1) Django version declaration. + +# If your application uses Django and requires a specific version of +# Django, uncomment the following block of three lines. Currently +# supported values for the Django version are '0.96' (the default), +# '1.0', and '1.1'. + +# # from google.appengine.dist import use_library +# # use_library('django', '1.0') +# # import django + + +# 2) Configuration constants. + +# DEBUG: True of False. When True, verbose messages are logged at the +# DEBUG level. Also, this flag is causes tracebacks to be shown in +# the web UI when an exception occurs. (Tracebacks are always logged +# at the ERROR level as well.) + +appstats_DEBUG = False + +# DUMP_LEVEL: -1, 0, 1 or 2. Controls how much debug output is +# written to the logs by the internal dump() function during event +# recording. -1 dumps nothing; 0 dumps one line of information; 1 +# dumps more informat and 2 dumps the maximum amount of information. +# You would only need to change this if you were debugging the +# recording implementation. + +appstats_DUMP_LEVEL = -1 + +# The following constants control the resolution and range of the +# memcache keys used to record information about individual requests. +# Two requests that are closer than KEY_DISTANCE milliseconds will be +# mapped to the same key (thus losing all information about the +# earlier of the two requests). Up to KEY_MODULUS distinct keys are +# generated; after KEY_DISTANCE * KEY_MODULUS milliseconds the key +# values roll over. Increasing KEY_MODULUS causes a proportional +# increase of the amount of data saved in memcache. Increasing +# KEY_DISTANCE causes a requests during a larger timespan to be +# recorded, at the cost of increasing risk of assigning the same key +# to two adjacent requests. + +appstats_KEY_DISTANCE = 100 +appstats_KEY_MODULUS = 1000 + +# The following constants control the namespace and key values used to +# store information in memcache. You can safely leave this alone. + +appstats_KEY_NAMESPACE = '__appstats__' +appstats_KEY_PREFIX = '__appstats__' +appstats_KEY_TEMPLATE = ':%06d' +appstats_PART_SUFFIX = ':part' +appstats_FULL_SUFFIX = ':full' +appstats_LOCK_SUFFIX = '' + +# Numerical limits on how much information is saved for each event. +# MAX_STACK limits the number of stack frames saved; MAX_LOCALS limits +# the number of local variables saved per stack frame. MAX_REPR +# limits the length of the string representation of each variable +# saved; MAX_DEPTH limits the nesting depth used when computing the +# string representation of structured variables (e.g. lists of lists). + +appstats_MAX_STACK = 10 +appstats_MAX_LOCALS = 10 +appstats_MAX_REPR = 100 +appstats_MAX_DEPTH = 10 + +# Regular expressions. These are matched against the 'code key' of a +# stack frame, which is a string of the form +# '::'. If the code key of a stack frame +# matches RE_STACK_BOTTOM, it and all remaining stack frames are +# skipped. If the code key matches RE_STACK_SKIP, that frame is not +# saved but subsequent frames may be saved. + +appstats_RE_STACK_BOTTOM = r'dev_appserver\.py' +appstats_RE_STACK_SKIP = r'recording\.py|apiproxy_stub_map\.py' + +# Timeout for memcache lock management, in seconds. + +appstats_LOCK_TIMEOUT = 1 + +# Timezone offset. This is used to convert recorded times (which are +# all in UTC) to local time. The default is US/Pacific winter time. + +appstats_TZOFFSET = 8*3600 + +# URL path (sans host) leading to the stats UI. Should match app.yaml. + +appstats_stats_url = '/stats' + +# Fraction of requests to record. Set this to a float between 0.0 +# and 1.0 to record that fraction of all requests. + +appstats_RECORD_FRACTION = 1.0 + +# List of dicts mapping env vars to regular expressions. Each dict +# specifies a set of filters to be 'and'ed together. The keys are +# environment variables, the values are *match* regular expressions. +# A request is recorded if it matches all filters of at least one +# dict. If the FILTER_LIST variable is empty, all requests are +# recorded. Missing environment variables are considered to have +# the empty string as value. If a regular expression starts with +# '!', the sense of the match is negated (the value should *not* +# match the expression). + +appstats_FILTER_LIST = [] + +# 3) Configuration functions. + +# should_record() can be used to record a random percentage of calls. +# The argument is the CGI or WSGI environment dict. The default +# implementation returns True iff the request matches FILTER_LIST (see +# above) *and* random.random() < RECORD_FRACTION. + +def appstats_should_record(env): + if appstats_FILTER_LIST: + logging.debug('FILTER_LIST: %r', appstats_FILTER_LIST) + for filter_dict in appstats_FILTER_LIST: + for key, regex in filter_dict.iteritems(): + negated = isinstance(regex, str) and regex.startswith('!') + if negated: + regex = regex[1:] + value = env.get(key, '') + if bool(re.match(regex, value)) == negated: + logging.debug('No match on %r for %s=%r', regex, key, value) + break + else: + logging.debug('Match on %r', filter_dict) + break + else: + logging.debug('Non-empty FILTER_LIST, but no filter matches') + return False + if appstats_RECORD_FRACTION >= 1.0: + return True + return random.random() < appstats_RECORD_FRACTION + +# The following functions are called by the UI code only; they don't +# affect the recorded information. + +# normalize_path() takes a path and returns an 'path key'. The path +# key is used by the UI to compute statistics for similar URLs. If +# your application has a large or infinite URL space (e.g. each issue +# in an issue tracker might have its own numeric URL), this function +# can be used to produce more meaningful statistics. + +def appstats_normalize_path(path): + return path + +# extract_key() is a lower-level function with the same purpose as +# normalize_key(). It can be used to lump different request methods +# (e.g. GET and POST) together, or conversely to use other information +# on the request object (mostly the query string) to produce a more +# fine-grained path key. The argument is a StatsProto object; this is +# a class defined in recording.py. Useful methods are: +# # - http_method() +# - http_path() +# - http_query() +# - http_status() +# # Note that the StatsProto argument is loaded only with summary +# information; this means you cannot access the request headers. + +def appstats_extract_key(request): + key = appstats_normalize_path(request.http_path()) + if request.http_method() != 'GET': + key = '%s %s' % (request.http_method(), key) + return key diff --git a/google_appengine/google/appengine/ext/appstats/static/app_engine_logo_sm.gif b/google_appengine/google/appengine/ext/appstats/static/app_engine_logo_sm.gif new file mode 100644 index 0000000000000000000000000000000000000000..b1e3339862b1942f581658b33d46ca13388b69fa GIT binary patch literal 2947 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/appstats/static/appstats_css.css b/google_appengine/google/appengine/ext/appstats/static/appstats_css.css new file mode 100755 index 0000000..95b9984 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/static/appstats_css.css @@ -0,0 +1,2 @@ +/* Copyright 2010 Google Inc. All Rights Reserved. */ +html,body,div,h1,h2,h3,h4,h5,h6,p,img,dl,dt,dd,ol,ul,li,table,caption,tbody,tfoot,thead,tr,th,td,form,fieldset,embed,object,applet{margin:0;padding:0;border:0;}body{font-size:62.5%;font-family:Arial,sans-serif;color:#000;background:#fff}a{color:#00c}a:active{color:#f00}a:visited{color:#551a8b}table{border-collapse:collapse;border-width:0;empty-cells:show}ul{padding:0 0 1em 1em}ol{padding:0 0 1em 1.3em}li{line-height:1.5em;padding:0 0 .5em 0}p{padding:0 0 1em 0}h1,h2,h3,h4,h5{padding:0 0 1em 0}h1,h2{font-size:1.3em}h3{font-size:1.1em}h4,h5,table{font-size:1em}sup,sub{font-size:.7em}input,select,textarea,option{font-family:inherit;font-size:inherit}.g-doc,.g-doc-1024,.g-doc-800{font-size:130%}.g-doc{width:100%;text-align:left}.g-section{width:100%;vertical-align:top;display:inline-block}*:first-child+html .g-section{display:block}* html .g-section{overflow:hidden}@-moz-document url-prefix(){.g-section{overflow:hidden}}@-moz-document url-prefix(){.g-section,tt:default{overflow:visible}}.g-section,.g-unit{zoom:1}.g-split .g-unit{text-align:right}.g-split .g-first{text-align:left}.g-doc-1024{width:73.074em;*width:71.313em;min-width:950px;margin:0 auto;text-align:left}.g-doc-800{width:57.69em;*width:56.3em;min-width:750px;margin:0 auto;text-align:left}.g-tpl-160 .g-unit,.g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-tpl-160 .g-unit,.g-unit .g-unit .g-unit .g-tpl-160 .g-unit{margin:0 0 0 160px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-unit .g-tpl-160 .g-first,.g-unit .g-tpl-160 .g-first,.g-tpl-160 .g-first{margin:0;width:160px;float:left}.g-tpl-160-alt .g-unit,.g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-tpl-160-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-160-alt .g-unit{margin:0 160px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-unit .g-tpl-160-alt .g-first,.g-unit .g-tpl-160-alt .g-first,.g-tpl-160-alt .g-first{margin:0;width:160px;float:right}.g-tpl-180 .g-unit,.g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-tpl-180 .g-unit,.g-unit .g-unit .g-unit .g-tpl-180 .g-unit{margin:0 0 0 180px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-unit .g-tpl-180 .g-first,.g-unit .g-tpl-180 .g-first,.g-tpl-180 .g-first{margin:0;width:180px;float:left}.g-tpl-180-alt .g-unit,.g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-tpl-180-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-180-alt .g-unit{margin:0 180px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-unit .g-tpl-180-alt .g-first,.g-unit .g-tpl-180-alt .g-first,.g-tpl-180-alt .g-first{margin:0;width:180px;float:right}.g-tpl-300 .g-unit,.g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-tpl-300 .g-unit,.g-unit .g-unit .g-unit .g-tpl-300 .g-unit{margin:0 0 0 300px;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-unit .g-tpl-300 .g-first,.g-unit .g-tpl-300 .g-first,.g-tpl-300 .g-first{margin:0;width:300px;float:left}.g-tpl-300-alt .g-unit,.g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-tpl-300-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-300-alt .g-unit{margin:0 300px 0 0;width:auto;float:none}.g-unit .g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-unit .g-tpl-300-alt .g-first,.g-unit .g-tpl-300-alt .g-first,.g-tpl-300-alt .g-first{margin:0;width:300px;float:right}.g-tpl-25-75 .g-unit,.g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-tpl-25-75 .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75 .g-unit{width:74.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-unit .g-tpl-25-75 .g-first,.g-unit .g-tpl-25-75 .g-first,.g-tpl-25-75 .g-first{width:24.999%;float:left;margin:0}.g-tpl-25-75-alt .g-unit,.g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-tpl-25-75-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-unit{width:24.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-unit .g-tpl-25-75-alt .g-first,.g-unit .g-tpl-25-75-alt .g-first,.g-tpl-25-75-alt .g-first{width:74.999%;float:right;margin:0}.g-tpl-75-25 .g-unit,.g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-tpl-75-25 .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25 .g-unit{width:24.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-unit .g-tpl-75-25 .g-first,.g-unit .g-tpl-75-25 .g-first,.g-tpl-75-25 .g-first{width:74.999%;float:left;margin:0}.g-tpl-75-25-alt .g-unit,.g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-tpl-75-25-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-unit{width:74.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-unit .g-tpl-75-25-alt .g-first,.g-unit .g-tpl-75-25-alt .g-first,.g-tpl-75-25-alt .g-first{width:24.999%;float:right;margin:0}.g-tpl-33-67 .g-unit,.g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-tpl-33-67 .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67 .g-unit{width:66.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-unit .g-tpl-33-67 .g-first,.g-unit .g-tpl-33-67 .g-first,.g-tpl-33-67 .g-first{width:32.999%;float:left;margin:0}.g-tpl-33-67-alt .g-unit,.g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-tpl-33-67-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-unit{width:32.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-unit .g-tpl-33-67-alt .g-first,.g-unit .g-tpl-33-67-alt .g-first,.g-tpl-33-67-alt .g-first{width:66.999%;float:right;margin:0}.g-tpl-67-33 .g-unit,.g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-tpl-67-33 .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33 .g-unit{width:32.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-unit .g-tpl-67-33 .g-first,.g-unit .g-tpl-67-33 .g-first,.g-tpl-67-33 .g-first{width:66.999%;float:left;margin:0}.g-tpl-67-33-alt .g-unit,.g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-tpl-67-33-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-unit{width:66.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-unit .g-tpl-67-33-alt .g-first,.g-unit .g-tpl-67-33-alt .g-first,.g-tpl-67-33-alt .g-first{width:32.999%;float:right;margin:0}.g-tpl-50-50 .g-unit,.g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-tpl-50-50 .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50 .g-unit{width:49.999%;float:right;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-unit .g-tpl-50-50 .g-first,.g-unit .g-tpl-50-50 .g-first,.g-tpl-50-50 .g-first{width:49.999%;float:left;margin:0}.g-tpl-50-50-alt .g-unit,.g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-tpl-50-50-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-unit{width:49.999%;float:left;margin:0}.g-unit .g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-unit .g-tpl-50-50-alt .g-first,.g-unit .g-tpl-50-50-alt .g-first,.g-tpl-50-50-alt .g-first{width:49.999%;float:right;margin:0}.g-tpl-nest{width:auto}.g-tpl-nest .g-section{display:inline}.g-tpl-nest .g-unit,.g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-tpl-nest .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest .g-unit{float:left;width:auto;margin:0}.g-tpl-nest-alt .g-unit,.g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-tpl-nest-alt .g-unit,.g-unit .g-unit .g-unit .g-tpl-nest-alt .g-unit{float:right;width:auto;margin:0}html>body .goog-inline-block{display:-moz-inline-box;display:inline-block;}.goog-inline-block{position:relative;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}.goog-tab{position:relative;border:1px solid #8ac;padding:4px 9px;color:#000;background:#e5ecf9;border-top-left-radius:2px;border-top-right-radius:2px;-moz-border-radius-topleft:2px;-webkit-border-top-left-radius:2px;-moz-border-radius-topright:2px;-webkit-border-top-right-radius:2px}.goog-tab-bar-top .goog-tab{margin:1px 4px 0 0;border-bottom:0;float:left}.goog-tab-bar-bottom .goog-tab{margin:0 4px 1px 0;border-top:0;float:left}.goog-tab-bar-start .goog-tab{margin:0 0 4px 1px;border-right:0}.goog-tab-bar-end .goog-tab{margin:0 1px 4px 0;border-left:0}.goog-tab-hover{text-decoration:underline;cursor:pointer}.goog-tab-disabled{color:#fff;background:#ccc;border-color:#ccc}.goog-tab-selected{background:#fff!important;color:black;font-weight:bold}.goog-tab-bar-top .goog-tab-selected{top:1px;margin-top:0;padding-bottom:5px}.goog-tab-bar-bottom .goog-tab-selected{top:-1px;margin-bottom:0;padding-top:5px}.goog-tab-bar-start .goog-tab-selected{left:1px;margin-left:0;padding-right:9px}.goog-tab-bar-end .goog-tab-selected{left:-1px;margin-right:0;padding-left:9px}.goog-tab-content{padding:.1em .8em .8em .8em;border:1px solid #8ac;border-top:none}.goog-tab-bar{position:relative;margin:0 0 0 5px;border:0;padding:0;list-style:none;cursor:default;outline:none}.goog-tab-bar-clear{border-top:1px solid #8ac;clear:both;height:0;overflow:hidden}.goog-tab-bar-start{float:left}.goog-tab-bar-end{float:right}* html .goog-tab-bar-start{margin-right:-3px}* html .goog-tab-bar-end{margin-left:-3px}.ae-table-plain{border-collapse:collapse;width:100%}.ae-table{border:1px solid #c5d7ef;border-collapse:collapse;width:100%}#bd h2.ae-table-title{background:#e5ecf9;margin:0;color:#000;font-size:1em;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}.ae-table-caption,.ae-table caption{border:1px solid #c5d7ef;background:#e5ecf9;-moz-margin-start:-1px}.ae-table caption{padding:3px 5px;text-align:left}.ae-table th,.ae-table td{background-color:#fff;padding:.35em 1em .25em .35em;margin:0}.ae-table thead th{font-weight:bold;text-align:left;background:#c5d7ef;vertical-align:bottom}.ae-table tfoot tr td{border-top:1px solid #c5d7ef;background-color:#e5ecf9}.ae-table td{border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even td,.ae-even th,.ae-even-top td,.ae-even-tween td,.ae-even-bottom td,ol.ae-even{background-color:#e9e9e9;border-top:1px solid #c5d7ef;border-bottom:1px solid #c5d7ef}.ae-even-top td{border-bottom:0}.ae-even-bottom td{border-top:0}.ae-even-tween td{border:0}.ae-table .ae-tween td{border:0}.ae-table .ae-tween-top td{border-bottom:0}.ae-table .ae-tween-bottom td{border-top:0}#bd .ae-table .cbc{width:1.5em;padding-right:0}.ae-table #ae-live td{background-color:#ffeac0}.ae-table-fixed{table-layout:fixed}.ae-table-fixed td,.ae-table-nowrap{overflow:hidden;white-space:nowrap}.ae-paginate strong{margin:0 .5em}tfoot .ae-paginate{text-align:right}.ae-table-caption .ae-paginate,.ae-table-caption .ae-orderby{padding:2px 5px}.g-doc{width:auto;margin:8px 10px 0 10px}#ae-logo{margin-bottom:0}#ae-appbar-lrg{margin:0 0 1.25em 0;padding:.2em .6em;background-color:#e5ecf9;border-top:1px solid #6b90da}#ae-appbar-lrg h1{font-size:1em;margin:0;padding:0}#ft p{text-align:center;margin-top:2.5em;padding-top:.5em;border-top:2px solid #c3d9ff}#bd h3{font-weight:bold;font-size:1.4em}#bd p{padding:0 0 1em 0}#ae-content{padding-left:1em;border-left:1px solid #6b90da;min-height:200px}.ae-table .ae-pager{background-color:#c5d7ef}#ae-nav ul{list-style-type:none;margin:0;padding:1em 0}#ae-nav ul li{padding:.1em 0 .1em .5em;margin-bottom:.3em}#ae-nav .ae-nav-selected{color:#44464a;display:block;font-weight:bold;background-color:#e5ecf9;border-bottom:1px solid #cedff2}a.ae-nav-selected{color:#44464a;text-decoration:none}#ae-nav ul li span.ae-nav-disabled{color:#666}#ae-nav ul ul{margin:0;padding:0 0 0 .5em}#ae-nav ul ul li{padding-left:.5em}#ae-nav ul li a,#ae-nav ul li span,#ae-nav ul ul li a{padding-left:.5em}#ae-nav li a:link,#ae-nav li a:visited{color:#00c}#ae-nav li a:link.ae-nav-selected,#ae-nav li a:visited.ae-nav-selected{color:#000;text-decoration:none}.ae-nav-group{padding:.5em;margin:0 .75em 0 0;background-color:#fffbe8;border:1px solid #fff1a9}.ae-nav-group h4{font-weight:bold;padding:auto auto .5em .5em;padding-left:.4em;margin-bottom:.5em;padding-bottom:0}.ae-nav-group ul{margin:0 0 .5em 0;padding:0 0 0 1.3em;list-style-type:none}.ae-nav-group ul li{padding-bottom:.5em}.ae-nav-group li a:link,.ae-nav-group li a:visited{color:#00c}.ae-nav-group li a:hover{color:#00c}#datastore_search{margin-bottom:1em}#hint{background-color:#f6f9ff;border:1px solid #e5ecf9;margin-bottom:1em;padding:0.5em 1em}#message{color:red;position:relative;bottom:6px}#pagetotal{float:right}#pagetotal .count{font-weight:bold}table.entities{border:1px solid #c5d7ef;border-collapse:collapse;width:100%;margin-bottom:0}table.entities th,table.entities td{padding:.25em 1.5em .5em .5em}table.entities th{font-weight:bold;text-align:left;background:#e5ecf9;white-space:nowrap}table.entities th a,table.entities th a:visited{color:black;text-decoration:none}table.entities td{background-color:#fff;text-align:left;vertical-align:top;cursor:pointer}table.entities tr.even td{background-color:#f9f9f9}div.entities{background-color:#c5d7ef;margin-top:0}#entities-pager,#entities-control{padding:.3em 1em .4em 1em}#entities-pager{text-align:right}.ae-page-number{margin:0 0.5em}.ae-page-selected{font-weight:bold}#ae-stats-hd span{font-weight:normal}#ae-rpc-label-col{width:85%}#ae-rpc-stats-col{width:15%}#ae-path-label-col{width:45%}#ae-path-reqs-col{width:10%}#ae-path-rpcs-col{width:10%}#ae-path-stats-col{width:35%}#ae-stats-refresh{margin-bottom:1em}.ae-table-wrapper-left{margin-right:.5em}.ae-table-wrapper-right{margin-left:.5em}#ae-req-history,#ae-rpc-traces{margin-top:1em}.ae-zippy,.ae-zippy-all{position:relative;top:1px;height:12px;width:12px}.goog-zippy-collapsed{background:transparent url(./plus.gif) no-repeat}.goog-zippy-expanded{background:transparent url(./minus.gif) no-repeat}td.ae-hanging-indent{padding-left:20px;text-indent:-20px}.ae-stats-request-link{text-decoration:none}.ae-table td.rpc-req{padding-left:20px;width:20em}#bd div.ae-table-title{background:#e5ecf9;margin:0;color:#000;padding:3px 0 3px 5px;border-left:1px solid #c5d7ef;border-right:1px solid #c5d7ef;border-top:1px solid #c5d7ef}#bd div.ae-table-title h2{font-size:1em;margin:0;padding:0}#bd div.ae-table-title h2.ae-zippy{padding-left:16px;text-decoration:underline;color:#00c}#ae-head-glance span,#ae-rpc-expand-all span,#ae-path-expand-all span,#ae-request-expand-all span{padding-right:.5em}.ae-action{color:#00c;text-decoration:underline;cursor:pointer}.ae-toggle{padding-left:16px;background-position:left center;background-repeat:no-repeat;cursor:pointer}.ae-minus{background-image:url(./minus.gif)}.ae-plus{background-image:url(./plus.gif)}#ae-stats-summary{margin-bottom:1em}#ae-stats-summary dt{float:left;text-align:right;margin-right:1em;font-weight:bold}.ae-stats-date{color:#666}.ae-stats-response-200{color:green}#ae-stats-summary dd{float:left}table.ae-stats-gantt-table{width:95%;border:1px solid #999}div.ae-stats-gantt-container{position:relative;width:100%;height:1em;background-color:#eeeeff}img.ae-stats-gantt-bar{border:0;height:1em;background-color:#7777ff;position:absolute;top:0}img.ae-stats-gantt-extra{border:0;height:0.5em;background-color:#ff6666;position:absolute;top:25%}span.ae-stats-gantt-inline{font-size:80%;position:absolute;top:0.1em;white-space:nowrap;overflow:hidden}a.ae-stats-gantt-link{text-decoration:none}div.ae-stats-gantt-axis{position:relative;width:100%;height:1em}img.ae-stats-gantt-tick{width:1px;height:1em;position:absolute;background-color:gray}span.ae-stats-gantt-scale{position:absolute} \ No newline at end of file diff --git a/google_appengine/google/appengine/ext/appstats/static/appstats_js.js b/google_appengine/google/appengine/ext/appstats/static/appstats_js.js new file mode 100755 index 0000000..5af7e74 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/static/appstats_js.js @@ -0,0 +1,82 @@ +/* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){function e(a){throw a;} +var h=true,j=null,k=false,p,r=this,aa=function(a,b,c){a=a.split(".");c=c||r;!(a[0]in c)&&c.execScript&&c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)if(!a.length&&b!==undefined)c[d]=b;else c=c[d]?c[d]:c[d]={}},ba=function(){},ca=function(a){a.Q=function(){return a.bc||(a.bc=new a)}},da=function(a){var b=typeof a;if(b=="object")if(a){if(a instanceof Array||!(a instanceof Object)&&Object.prototype.toString.call(a)=="[object Array]"||typeof a.length=="number"&&typeof a.splice!="undefined"&& +typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("splice"))return"array";if(!(a instanceof Object)&&(Object.prototype.toString.call(a)=="[object Function]"||typeof a.call!="undefined"&&typeof a.propertyIsEnumerable!="undefined"&&!a.propertyIsEnumerable("call")))return"function"}else return"null";else if(b=="function"&&typeof a.call=="undefined")return"object";return b},ea=function(a){return da(a)=="array"},fa=function(a){var b=da(a);return b=="array"||b=="object"&&typeof a.length== +"number"},s=function(a){return typeof a=="string"},t=function(a){return da(a)=="function"},ga=function(a){a=da(a);return a=="object"||a=="array"||a=="function"},u=function(a){if(a.hasOwnProperty&&a.hasOwnProperty(ha))return a[ha];a[ha]||(a[ha]=++ia);return a[ha]},ha="closure_uid_"+Math.floor(Math.random()*2147483648).toString(36),ia=0,ja=function(a){var b=Array.prototype.slice.call(arguments,1);return function(){var c=Array.prototype.slice.call(arguments);c.unshift.apply(c,b);return a.apply(this, +c)}},v=function(a,b){function c(){}c.prototype=b.prototype;a.d=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ka=function(a){this.stack=Error().stack||"";if(a)this.message=String(a)};v(ka,Error);ka.prototype.name="CustomError";var la=function(a){for(var b=1;b")!=-1)a=a.replace(pa,">");if(a.indexOf('"')!=-1)a=a.replace(qa, +""");return a}},na=/&/g,oa=//g,qa=/\"/g,ra=/[&<>\"]/,ua=function(a,b){var c=0,d=ma(String(a)).split("."),f=ma(String(b)).split("."),g=Math.max(d.length,f.length);for(var i=0;c==0&&ib)return 1;return 0};var va=function(a,b){b.unshift(a);ka.call(this,la.apply(j,b));b.shift();this.lc=a};v(va,ka);va.prototype.name="AssertionError";var wa=function(a,b,c,d){var f="Assertion failed";if(c){f+=": "+c;var g=d}else if(a){f+=": "+a;g=b}e(new va(""+f,g||[]))},xa=function(a,b){!a&&wa("",j,b,Array.prototype.slice.call(arguments,2))},ya=function(a,b){typeof a!="number"&&wa("Expected number but got %s.",[a],b,Array.prototype.slice.call(arguments,2));return a};var w=Array.prototype,za=w.indexOf?function(a,b,c){xa(a||s(a));ya(a.length);return w.indexOf.call(a,b,c)}:function(a,b,c){c=c==j?0:c<0?Math.max(0,a.length+c):c;if(s(a)){if(!s(b)||b.length!=1)return-1;return a.indexOf(b,c)}for(c=c;c=0},Da=function(a,b){var c=za(a,b),d;if(d=c>=0){xa(a||s(a));ya(a.length);w.splice.call(a,c,1).length==1}return d},Ea=function(){return w.concat.apply(w,arguments)},Fa=function(a){if(ea(a))return Ea(a);else{var b=[],c=0;for(var d=a.length;c=0)};var fb,gb=function(a){return(a=a.className)&&typeof a.split=="function"?a.split(/\s+/):[]},C=function(a){var b=gb(a),c;c=Ga(arguments,1);var d=0;for(var f=0;f");c=c.join("")}c=a.createElement(c);if(d)if(s(d))c.className=d;else nb(c,d);b.length>2&&qb(a,c,b,2);return c},qb=function(a,b,c,d){function f(i){if(i)b.appendChild(s(i)?a.createTextNode(i):i)}for(d=d;d +0)?Aa(rb(g)?Fa(g):g,f):f(g)}},sb=function(a){return a&&a.parentNode?a.parentNode.removeChild(a):j},tb=function(a,b){if(a.contains&&b.nodeType==1)return a==b||a.contains(b);if(typeof a.compareDocumentPosition!="undefined")return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a},jb=function(a){return a.nodeType==9?a:a.ownerDocument||a.document},ub=function(a,b){if("textContent"in a)a.textContent=b;else if(a.firstChild&&a.firstChild.nodeType==3){for(;a.lastChild!= +a.firstChild;)a.removeChild(a.lastChild);a.firstChild.data=b}else{for(var c;c=a.firstChild;)a.removeChild(c);a.appendChild(jb(a).createTextNode(b))}},vb={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},wb={IMG:" ",BR:"\n"},xb=function(a){var b=a.getAttributeNode("tabindex");if(b&&b.specified){a=a.tabIndex;return typeof a=="number"&&a>=0}return k},yb=function(a,b,c){if(!(a.nodeName in vb))if(a.nodeType==3)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in +wb)b.push(wb[a.nodeName]);else for(a=a.firstChild;a;){yb(a,b,c);a=a.nextSibling}},rb=function(a){if(a&&typeof a.length=="number")if(ga(a))return typeof a.item=="function"||typeof a.item=="string";else if(t(a))return typeof a.item=="function";return k},ib=function(a){this.G=a||r.document||document};p=ib.prototype;p.Fa=kb;p.c=function(a){return s(a)?this.G.getElementById(a):a};p.m=function(){return ob(this.G,arguments)};p.createElement=function(a){return this.G.createElement(a)};p.createTextNode=function(a){return this.G.createTextNode(a)}; +p.appendChild=function(a,b){a.appendChild(b)};p.contains=tb;var E=function(){};E.prototype.Sa=k;E.prototype.L=function(){if(!this.Sa){this.Sa=h;this.f()}};E.prototype.f=function(){};var zb,F=function(a,b){this.type=a;this.currentTarget=this.target=b};v(F,E);p=F.prototype;p.f=function(){delete this.type;delete this.target;delete this.currentTarget};p.U=k;p.ka=h;p.stopPropagation=function(){this.U=h};p.preventDefault=function(){this.ka=k};var G=function(a,b){a&&this.ta(a,b)};v(G,F);var Ab=[1,4,2];p=G.prototype;p.target=j;p.relatedTarget=j;p.offsetX=0;p.offsetY=0;p.clientX=0;p.clientY=0;p.screenX=0;p.screenY=0;p.button=0;p.keyCode=0;p.charCode=0;p.ctrlKey=k;p.altKey=k;p.shiftKey=k;p.metaKey=k;p.hc=k;p.M=j; +p.ta=function(a,b){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(y)try{d=d.nodeName&&d}catch(f){d=j}}else if(c=="mouseover")d=a.fromElement;else if(c=="mouseout")d=a.toElement;this.relatedTarget=d;this.offsetX=a.offsetX!==undefined?a.offsetX:a.layerX;this.offsetY=a.offsetY!==undefined?a.offsetY:a.layerY;this.clientX=a.clientX!==undefined?a.clientX:a.pageX;this.clientY=a.clientY!==undefined?a.clientY:a.pageY;this.screenX=a.screenX||0; +this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||(c=="keypress"?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.hc=Za?a.metaKey:a.ctrlKey;this.M=a;delete this.ka;delete this.U};var Bb=function(a,b){return x?a.type=="click"?b==0:!!(a.M.button&Ab[b]):a.M.button==b};G.prototype.stopPropagation=function(){this.U=h;if(this.M.stopPropagation)this.M.stopPropagation();else this.M.cancelBubble=h}; +var Cb=x&&!B("8");G.prototype.preventDefault=function(){this.ka=k;var a=this.M;if(a.preventDefault)a.preventDefault();else{a.returnValue=k;if(Cb)try{if(a.ctrlKey||a.keyCode>=112&&a.keyCode<=123)a.keyCode=-1}catch(b){}}};G.prototype.f=function(){G.d.f.call(this);this.relatedTarget=this.currentTarget=this.target=this.M=j};var I=function(a,b){this.xb=b;this.$=[];if(a>this.xb)e(Error("[goog.structs.SimplePool] Initial cannot be greater than max"));for(var c=0;c=0),i;Pb=function(q){i=q};if(g){Kb=function(){return Db(l)};Lb=function(q){Fb(l,q)};Mb=function(){return Db(m)};Nb=function(q){Fb(m,q)};Ob=function(){return Db(n)};Qb=function(){Fb(n,c())};Rb=function(){return Db(A)};Sb=function(q){Fb(A,q)};Tb=function(){return Db(o)};Ub=function(q){Fb(o,q)}; +var l=new I(0,600);l.J=a;var m=new I(0,600);m.J=b;var n=new I(0,600);n.J=c;var A=new I(0,600);A.J=d;var o=new I(0,600);o.J=f}else{Kb=a;Lb=ba;Mb=b;Nb=ba;Ob=c;Qb=ba;Rb=d;Sb=ba;Tb=f;Ub=ba}})();var Vb={},J={},K={},Wb={},L=function(a,b,c,d,f){if(b)if(ea(b)){for(var g=0;g=0;n--){var A=m[n];if((g||b==A.type)&&(i||c==A.capture)){M(A.key);d++}}});else{a=u(a);if(K[a]){a=K[a];for(f=a.length-1;f>=0;f--){var l=a[f];if((g||b==l.type)&&(i||c==l.capture)){M(l.key);d++}}}}return d},Zb=function(a,b,c){var d=J;if(b in d){d=d[b];if(c in d){d=d[c];a=u(a);if(d[a])return d[a]}}return j},Xb=function(a){if(a in Wb)return Wb[a];return Wb[a]="on"+a},cc=function(a,b,c,d,f){var g=1;b=u(b);if(a[b]){a.B--;a=a[b];if(a.Ka)a.Ka++;else a.Ka=1;try{var i=a.length;for(var l=0;l=0&&i.B;H--){n.currentTarget=o[H];g&=cc(i,o[H],d,h,n)}if(m){i=f[k];i.B=i.F;for(H=0;!n.U&&H=0&&g.B;i--){a.currentTarget=c[i];b&=cc(g,c[i],a.type,h,a)&&a.ka!=k}}if(k in f){g=f[k];g.B=g.F;if(d)for(i=0;!a.U&&itc(this))e(Error("Child component index out of bounds"));if(!this.r||!this.p){this.r={};this.p=[]}if(a.h==this){this.r[oc(a)]=a;Da(this.p,a)}else Ka(this.r,oc(a),a);rc(a,this);Ha(this.p,b,0,a);if(a.e&&this.e&&a.h==this){c=this.N();c.insertBefore(a.c(),c.childNodes[b]||j)}else if(c){this.b||this.m();c=Q(this,b+1);b=this.N();c=c?c.b:j;if(a.e)e(Error("Component already rendered"));a.b||a.m();b?b.insertBefore(a.b, +c||j):a.z.G.body.appendChild(a.b);if(!a.h||a.h.e)a.H()}else this.e&&!a.e&&a.b&&a.H()};p.N=function(){return this.b};var uc=function(a){if(a.va==j){var b;a:{b=a.e?a.b:a.z.G.body;var c=jb(b);if(c.defaultView&&c.defaultView.getComputedStyle)if(b=c.defaultView.getComputedStyle(b,"")){b=b.direction;break a}b=j}a.va="rtl"==(b||((a.e?a.b:a.z.G.body).currentStyle?(a.e?a.b:a.z.G.body).currentStyle.direction:j)||(a.e?a.b:a.z.G.body).style.direction)}return a.va}; +P.prototype.ya=function(a){if(this.e)e(Error("Component already rendered"));this.va=a};var tc=function(a){return a.p?a.p.length:0},Q=function(a,b){return a.p?a.p[b]||j:j},sc=function(a,b,c){a.p&&Aa(a.p,b,c)},vc=function(a,b){return a.p&&b?za(a.p,b):-1};P.prototype.removeChild=function(a,b){if(a){var c=s(a)?a:oc(a);a=this.r&&c?La(this.r,c)||j:j;if(c&&a){Ja(this.r,c);Da(this.p,a);if(b){a.Z();a.b&&sb(a.b)}rc(a,j)}}if(!a)e(Error("Child is not in parent component"));return a};var wc=function(a,b){if(y){a.setAttribute("role",b);a.mc=b}};var yc=function(a,b,c,d,f){if(!x&&!(z&&B("525")))return h;if(Za&&f)return xc(a);if(f&&!d)return k;if(!c&&(b==17||b==18))return k;if(x&&d&&b==a)return k;switch(a){case 13:return h;case 27:return!z}return xc(a)},xc=function(a){if(a>=48&&a<=57)return h;if(a>=96&&a<=106)return h;if(a>=65&&a<=90)return h;if(z&&a==0)return h;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return h;default:return k}};var R=function(a){a&&zc(this,a)};v(R,gc);p=R.prototype;p.b=j;p.Ia=j;p.cb=j;p.Ja=j;p.ua=-1;p.ca=-1; +var Ac={"3":13,"12":144,"63232":38,"63233":40,"63234":37,"63235":39,"63236":112,"63237":113,"63238":114,"63239":115,"63240":116,"63241":117,"63242":118,"63243":119,"63244":120,"63245":121,"63246":122,"63247":123,"63248":44,"63272":46,"63273":36,"63275":35,"63276":33,"63277":34,"63289":144,"63302":45},Bc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},Cc={61:187, +59:186},Dc=x||z&&B("525");R.prototype.Tb=function(a){if(Dc&&!yc(a.keyCode,this.ua,a.shiftKey,a.ctrlKey,a.altKey))this.handleEvent(a);else this.ca=y&&a.keyCode in Cc?Cc[a.keyCode]:a.keyCode};R.prototype.Ub=function(){this.ca=this.ua=-1}; +R.prototype.handleEvent=function(a){var b=a.M,c,d;if(x&&a.type=="keypress"){c=this.ca;d=c!=13&&c!=27?b.keyCode:0}else if(z&&a.type=="keypress"){c=this.ca;d=b.charCode>=0&&b.charCode<63232&&xc(c)?b.charCode:0}else if(Xa){c=this.ca;d=xc(c)?b.keyCode:0}else{c=b.keyCode||this.ca;d=b.charCode||0;if(Za&&d==63&&!c)c=191}var f=c,g=b.keyIdentifier;if(c)if(c>=63232&&c in Ac)f=Ac[c];else{if(c==25&&a.shiftKey)f=9}else if(g&&g in Bc)f=Bc[g];a=f==this.ua;this.ua=f;b=new Ec(f,d,a,b);try{this.dispatchEvent(b)}finally{b.L()}}; +R.prototype.c=function(){return this.b};var zc=function(a,b){a.Ja&&a.detach();a.b=b;a.Ia=L(a.b,"keypress",a);a.cb=L(a.b,"keydown",a.Tb,k,a);a.Ja=L(a.b,"keyup",a.Ub,k,a)};R.prototype.detach=function(){if(this.Ia){M(this.Ia);M(this.cb);M(this.Ja);this.Ja=this.cb=this.Ia=j}this.b=j;this.ca=this.ua=-1};R.prototype.f=function(){R.d.f.call(this);this.detach()};var Ec=function(a,b,c,d){d&&this.ta(d,void 0);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};v(Ec,G);var Gc=function(a,b){if(!a)e(Error("Invalid class name "+a));if(!t(b))e(Error("Invalid decorator function "+b));Fc[a]=b},Hc={},Fc={};var Ic=function(){},Jc;ca(Ic);p=Ic.prototype;p.ea=function(){};p.m=function(a){return a.Fa().m("div",this.oa(a).join(" "),a.la)};p.N=function(a){return a};p.na=function(a,b,c){if(a=a.c?a.c():a)if(x&&!B("7")){var d=Kc(this,gb(a),b);d.push(b);ja(c?C:hb,a).apply(j,d)}else c?C(a,b):hb(a,b)};p.W=function(){return h}; +p.K=function(a,b){b.id&&pc(a,b.id);var c=this.N(b);if(c&&c.firstChild)Lc(a,c.firstChild.nextSibling?Fa(c.childNodes):c.firstChild);else a.la=j;var d=0,f=this.o(),g=this.o(),i=k,l=k;c=k;var m=gb(b);Aa(m,function(o){if(!i&&o==f){i=h;if(g==f)l=h}else if(!l&&o==g)l=h;else{var q=d;if(!this.Ab){this.Ea||Mc(this);this.Ab=Ma(this.Ea)}o=parseInt(this.Ab[o],10);d=q|(isNaN(o)?0:o)}},this);a.l=d;if(!i){m.push(f);if(g==f)l=h}l||m.push(g);var n=a.A;n&&m.push.apply(m,n);if(x&&!B("7")){var A=Kc(this,m);if(A.length> +0){m.push.apply(m,A);c=h}}if(!i||!l||n||c)b.className=m.join(" ");return b};p.bb=function(a){uc(a)&&this.ya(a.c(),h);a.j()&&this.xa(a,a.I())};p.Ma=function(a,b){kc(a,!b,!x&&!Xa)};p.ya=function(a,b){this.na(a,this.o()+"-rtl",b)};p.ba=function(a){var b;if(a.w&32&&(b=a.n()))return xb(b);return k};p.xa=function(a,b){var c;if(a.w&32&&(c=a.n())){if(!b&&a.l&32){try{c.blur()}catch(d){}a.l&32&&a.pa(j)}if(xb(c)!=b){c=c;if(b)c.tabIndex=0;else c.removeAttribute("tabIndex")}}};p.za=function(a,b){ic(a,b)}; +p.v=function(a,b,c){var d=a.c();if(d){var f=Nc(this,b);f&&this.na(a,f,c);if(y){Jc||(Jc=Pa(1,"disabled",4,"pressed",8,"selected",16,"checked",64,"expanded"));(a=Jc[b])&&y&&d.setAttribute("aria-"+a,c)}}};p.n=function(a){return a.c()};p.o=function(){return"goog-control"};p.oa=function(a){var b=this.o(),c=[b],d=this.o();d!=b&&c.push(d);b=a.l;for(d=[];b;){var f=b&-b;d.push(Nc(this,f));b&=~f}c.push.apply(c,d);(a=a.A)&&c.push.apply(c,a);x&&!B("7")&&c.push.apply(c,Kc(this,c));return c}; +var Kc=function(a,b,c){var d=[];if(c)b=b.concat([c]);Aa([],function(f){if(Ba(f,ja(Ca,b))&&(!c||Ca(f,c)))d.push(f.join("_"))});return d},Nc=function(a,b){a.Ea||Mc(a);return a.Ea[b]},Mc=function(a){var b=a.o();a.Ea=Pa(1,b+"-disabled",2,b+"-hover",4,b+"-active",8,b+"-selected",16,b+"-checked",32,b+"-focused",64,b+"-open")};var S=function(a,b,c){P.call(this,c);if(!(b=b)){b=this.constructor;var d;for(;b;){d=u(b);if(d=Hc[d])break;b=b.d?b.d.constructor:j}b=d?t(d.Q)?d.Q():new d:j}this.a=b;this.la=a};v(S,P);p=S.prototype;p.la=j;p.l=0;p.w=39;p.Ib=255;p.Na=0;p.q=h;p.A=j;p.sa=h;p.Ca=k;p.n=function(){return this.a.n(this)};p.Ga=function(){return this.t||(this.t=new R)};p.pb=function(){return this.a}; +p.na=function(a,b){if(b){if(a){if(this.A)Ca(this.A,a)||this.A.push(a);else this.A=[a];this.a.na(this,a,h)}}else if(a&&this.A){Da(this.A,a);if(this.A.length==0)this.A=j;this.a.na(this,a,k)}};p.m=function(){var a=this.a.m(this);this.b=a;if(y){var b=this.a.ea();b&&wc(a,b)}this.Ca||this.a.Ma(a,k);this.I()||this.a.za(a,k)};p.N=function(){return this.a.N(this.c())};p.W=function(a){return this.a.W(a)}; +p.Ra=function(a){this.b=a=this.a.K(this,a);if(y){var b=this.a.ea();b&&wc(a,b)}this.Ca||this.a.Ma(a,k);this.q=a.style.display!="none"};p.H=function(){S.d.H.call(this);this.a.bb(this);if(this.w&-2){this.sa&&Oc(this,h);if(this.w&32){var a=this.n();if(a){var b=this.Ga();zc(b,a);N(N(N(qc(this),b,"key",this.R),a,"focus",this.qa),a,"blur",this.pa)}}}}; +var Oc=function(a,b){var c=qc(a),d=a.c();if(b){N(N(N(N(c,d,"mouseover",a.Za),d,"mousedown",a.ra),d,"mouseup",a.$a),d,"mouseout",a.Ya);x&&N(c,d,"dblclick",a.qb)}else{O(O(O(O(c,d,"mouseover",a.Za),d,"mousedown",a.ra),d,"mouseup",a.$a),d,"mouseout",a.Ya);x&&O(c,d,"dblclick",a.qb)}};S.prototype.Z=function(){S.d.Z.call(this);this.t&&this.t.detach();this.I()&&this.j()&&this.a.xa(this,k)};S.prototype.f=function(){S.d.f.call(this);if(this.t){this.t.L();delete this.t}delete this.a;this.A=this.la=j}; +var Lc=function(a,b){a.la=b};p=S.prototype;p.ya=function(a){S.d.ya.call(this,a);var b=this.c();b&&this.a.ya(b,a)};p.Ma=function(a){this.Ca=a;var b=this.c();b&&this.a.Ma(b,a)};p.I=function(){return this.q};p.za=function(a,b){if(b||this.q!=a&&this.dispatchEvent(a?"show":"hide")){var c=this.c();c&&this.a.za(c,a);this.j()&&this.a.xa(this,a);this.q=a;return h}return k};p.j=function(){return!!!(this.l&1)}; +p.wa=function(a){var b=this.h;if(!(b&&typeof b.j=="function"&&!b.j())&&T(this,1,!a)){if(!a){this.setActive(k);this.D(k)}this.I()&&this.a.xa(this,a);this.v(1,!a)}};p.D=function(a){T(this,2,a)&&this.v(2,a)};p.setActive=function(a){T(this,4,a)&&this.v(4,a)};var Pc=function(a,b){T(a,8,b)&&a.v(8,b)},Qc=function(a,b){T(a,64,b)&&a.v(64,b)};S.prototype.v=function(a,b){if(this.w&a&&b!=!!(this.l&a)){this.a.v(this,a,b);this.l=b?this.l|a:this.l&~a}}; +var Rc=function(a,b,c){if(a.e&&a.l&b&&!c)e(Error("Component already rendered"));!c&&a.l&b&&a.v(b,k);a.w=c?a.w|b:a.w&~b},U=function(a,b){return!!(a.Ib&b)&&!!(a.w&b)},T=function(a,b,c){return!!(a.w&b)&&!!(a.l&b)!=c&&(!(a.Na&b)||a.dispatchEvent(nc(b,c)))&&!a.Sa};S.prototype.Za=function(a){!Sc(a,this.c())&&this.dispatchEvent("enter")&&this.j()&&U(this,2)&&this.D(h)};S.prototype.Ya=function(a){if(!Sc(a,this.c())&&this.dispatchEvent("leave")){U(this,4)&&this.setActive(k);U(this,2)&&this.D(k)}}; +var Sc=function(a,b){return!!a.relatedTarget&&tb(b,a.relatedTarget)};S.prototype.ra=function(a){if(this.j()){U(this,2)&&this.D(h);if(Bb(a,0)){U(this,4)&&this.setActive(h);this.a.ba(this)&&this.n().focus()}}!this.Ca&&Bb(a,0)&&a.preventDefault()};S.prototype.$a=function(a){if(this.j()){U(this,2)&&this.D(h);this.l&4&&Tc(this,a)&&U(this,4)&&this.setActive(k)}};S.prototype.qb=function(a){this.j()&&Tc(this,a)}; +var Tc=function(a,b){if(U(a,16)){var c=!!!(a.l&16);T(a,16,c)&&a.v(16,c)}U(a,8)&&Pc(a,h);U(a,64)&&Qc(a,!!!(a.l&64));c=new F("action",a);if(b){var d=["altKey","ctrlKey","metaKey","shiftKey","platformModifierKey"],f;for(var g=0;f=d[g];g++)c[f]=b[f]}return a.dispatchEvent(c)};S.prototype.qa=function(){U(this,32)&&T(this,32,h)&&this.v(32,h)};S.prototype.pa=function(){U(this,4)&&this.setActive(k);U(this,32)&&T(this,32,k)&&this.v(32,k)}; +S.prototype.R=function(a){if(this.I()&&this.j()&&this.Xa(a)){a.preventDefault();a.stopPropagation();return h}return k};S.prototype.Xa=function(a){return a.keyCode==13&&Tc(this,a)};if(!t(S))e(Error("Invalid component class "+S));if(!t(Ic))e(Error("Invalid renderer class "+Ic));var Uc=u(S);Hc[Uc]=Ic;Gc("goog-control",function(){return new S(j)});var Vc=function(){};v(Vc,Ic);ca(Vc);Vc.prototype.m=function(a){return a.Fa().m("div",this.o())};Vc.prototype.K=function(a,b){if(b.tagName=="HR"){var c=b;b=this.m(a);c.parentNode&&c.parentNode.insertBefore(b,c);sb(c)}else C(b,this.o());return b};Vc.prototype.o=function(){return"goog-menuseparator"};var Wc=function(a,b){S.call(this,j,a||Vc.Q(),b);Rc(this,1,k);Rc(this,2,k);Rc(this,4,k);Rc(this,32,k);this.l=1};v(Wc,S);Wc.prototype.H=function(){Wc.d.H.call(this);wc(this.c(),"separator")};Gc("goog-menuseparator",function(){return new Wc});var V=function(){};ca(V);V.prototype.ea=function(){};var Xc=function(a,b,c){if(b)b.tabIndex=c?0:-1};p=V.prototype;p.m=function(a){return a.Fa().m("div",this.oa(a).join(" "))};p.N=function(a){return a};p.W=function(a){return a.tagName=="DIV"};p.K=function(a,b){b.id&&pc(a,b.id);var c=this.o(),d=k,f=gb(b);f&&Aa(f,function(g){if(g==c)d=h;else g&&this.ib(a,g,c)},this);d||C(b,c);Yc(this,a,b);return b}; +p.ib=function(a,b,c){if(b==c+"-disabled")a.wa(k);else if(b==c+"-horizontal")Zc(a,"horizontal");else b==c+"-vertical"&&Zc(a,"vertical")};var Yc=function(a,b,c,d){if(c){a=d||c.firstChild;for(;a&&a.parentNode==c;){d=a.nextSibling;if(a.nodeType==1){var f;a:{f=void 0;var g=gb(a),i=0;for(var l=g.length;i-1&&b!=this.k){var c=Q(this,this.k);c&&c.D(k);this.k=b;c=Q(this,this.k);this.ia&&c.setActive(h);if(this.gc&&this.g&&c!=this.g)c.w&64?Qc(c,h):Qc(this.g,k)}b=this.c();a=a.target.c().id;y&&b.setAttribute("aria-activedescendant",a)};p.$b=function(a){if(a.target==Q(this,this.k))this.k=-1;a=this.c();y&&a.setAttribute("aria-activedescendant","")};p.Vb=function(a){if((a=a.target)&&a!=this.g&&a.h==this){this.g&&Qc(this.g,k);this.g=a}}; +p.Pb=function(a){if(a.target==this.g)this.g=j};p.ra=function(a){if(this.Y)this.ia=h;var b=this.n(),c;a:{if(b)if((c=b.getAttributeNode("tabindex"))&&c.specified){c=b.tabIndex;c=typeof c=="number"&&c>=0;break a}c=k}c?b.focus():a.preventDefault()};p.Qb=function(){this.ia=k}; +p.Ob=function(a){var b;a:{b=a.target;if(this.O)for(var c=this.c();b&&b.parentNode&&b!=c;){var d=b.id;if(d in this.O){b=this.O[d];break a}b=b.parentNode}b=j}if(b)switch(a.type){case "mousedown":b.ra(a);break;case "mouseup":b.$a(a);break;case "mouseover":b.Za(a);break;case "mouseout":b.Ya(a);break}};p.qa=function(){};p.pa=function(){bd(this,-1);this.ia=k;this.g&&Qc(this.g,k)};p.R=function(a){if(this.j()&&this.I()&&(tc(this)!=0||this.ub)&&this.Xa(a)){a.preventDefault();a.stopPropagation();return h}return k}; +p.Xa=function(a){var b=Q(this,this.k);if(b&&typeof b.R=="function"&&b.R(a))return h;if(this.g&&this.g!=b&&typeof this.g.R=="function"&&this.g.R(a))return h;switch(a.keyCode){case 27:if(this.ba())this.n().blur();else return k;break;case 36:cd(this);break;case 35:dd(this);break;case 38:if(this.T=="vertical")ed(this);else return k;break;case 37:if(this.T=="horizontal")uc(this)?fd(this):ed(this);else return k;break;case 40:if(this.T=="vertical")fd(this);else return k;break;case 39:if(this.T=="horizontal")uc(this)? +ed(this):fd(this);else return k;break;default:return k}return h};var $c=function(a,b){var c=b.c();c=c.id||(c.id=oc(b));if(!a.O)a.O={};a.O[c]=b};W.prototype.Ba=function(a,b){W.d.Ba.call(this,a,b)};W.prototype.Pa=function(a,b,c){a.Na|=2;a.Na|=64;if(this.ba()||!this.Gb)Rc(a,32,k);a.e&&k!=a.sa&&Oc(a,k);a.sa=k;W.d.Pa.call(this,a,b,c);c&&this.e&&$c(this,a);b<=this.k&&this.k++}; +W.prototype.removeChild=function(a,b){var c=vc(this,a);if(c!=-1)if(c==this.k)a.D(k);else c-1&&Q(a,a.k).D(k)}; +W.prototype.D=function(a){bd(this,vc(this,a))};var cd=function(a){gd(a,function(b,c){return(b+1)%c},tc(a)-1)},dd=function(a){gd(a,function(b,c){b--;return b<0?c-1:b},0)},fd=function(a){gd(a,function(b,c){return(b+1)%c},a.k)},ed=function(a){gd(a,function(b,c){b--;return b<0?c-1:b},a.k)},gd=function(a,b,c){c=c<0?vc(a,a.g):c;var d=tc(a);c=b.call(a,c,d);for(var f=0;f<=d;){var g=Q(a,c);if(g&&g.I()&&g.j()&&g.w&2){a.gb(c);return h}f++;c=b.call(a,c,d)}return k};W.prototype.gb=function(a){bd(this,a)};var hd=function(){};v(hd,Ic);ca(hd);p=hd.prototype;p.o=function(){return"goog-tab"};p.ea=function(){return"tab"};p.m=function(a){var b=hd.d.m.call(this,a);(a=a.Wa())&&this.jb(b,a);return b};p.K=function(a,b){b=hd.d.K.call(this,a,b);var c=this.Wa(b);if(c)a.Cb=c;if(a.l&8)if((c=a.h)&&t(c.da)){a.v(8,k);c.da(a)}return b};p.Wa=function(a){return a.title||""};p.jb=function(a,b){if(a)a.title=b||""};var id=function(a,b,c){S.call(this,a,b||hd.Q(),c);Rc(this,8,h);this.Na|=9};v(id,S);id.prototype.Wa=function(){return this.Cb};id.prototype.jb=function(a){this.pb().jb(this.c(),a);this.Cb=a};Gc("goog-tab",function(){return new id(j)});var X=function(){};v(X,V);ca(X);X.prototype.o=function(){return"goog-tab-bar"};X.prototype.ea=function(){return"tablist"};X.prototype.ib=function(a,b,c){if(!this.vb){this.Da||jd(this);this.vb=Ma(this.Da)}var d=this.vb[b];if(d){Zc(a,kd(d));a.wb=d}else X.d.ib.call(this,a,b,c)};X.prototype.oa=function(a){var b=X.d.oa.call(this,a);this.Da||jd(this);b.push(this.Da[a.wb]);return b};var jd=function(a){var b=a.o();a.Da=Pa("top",b+"-top","bottom",b+"-bottom","start",b+"-start","end",b+"-end")};var Y=function(a,b,c){a=a||"top";Zc(this,kd(a));this.wb=a;W.call(this,this.T,b||X.Q(),c);b=qc(this);N(b,this,"select",this.Yb);N(b,this,"unselect",this.Zb);N(b,this,"disable",this.Wb);N(b,this,"hide",this.Xb)};v(Y,W);p=Y.prototype;p.Hb=h;p.C=j;p.f=function(){Y.d.f.call(this);this.C=j};p.removeChild=function(a,b){ld(this,a);return Y.d.removeChild.call(this,a,b)};p.gb=function(a){Y.d.gb.call(this,a);this.Hb&&this.da(Q(this,a))};p.da=function(a){if(a)Pc(a,h);else this.C&&Pc(this.C,k)}; +var ld=function(a,b){if(b&&b==a.C){var c=vc(a,b);for(var d=c-1;b=Q(a,d);d--)if(b.I()&&b.j()){a.da(b);return}for(c=c+1;b=Q(a,c);c++)if(b.I()&&b.j()){a.da(b);return}a.da(j)}};p=Y.prototype;p.Yb=function(a){this.C&&this.C!=a.target&&Pc(this.C,k);this.C=a.target};p.Zb=function(a){if(a.target==this.C)this.C=j};p.Wb=function(a){ld(this,a.target)};p.Xb=function(a){ld(this,a.target)};p.qa=function(){Q(this,this.k)||this.D(this.C||Q(this,0))};var kd=function(a){return a=="start"||a=="end"?"vertical":"horizontal"}; +Gc("goog-tab-bar",function(){return new Y});var Z=function(a,b,c,d){function f(i){if(i){i.tabIndex=0;L(i,"click",g.ec,k,g);L(i,"keydown",g.fc,k,g)}}this.X=D(a)||j;this.ma=D(d||j);this.Ta=(this.db=t(b)?b:j)||!b?j:D(b);this.i=c==h;var g=this;f(this.X);f(this.ma);this.V(this.i)};v(Z,gc);Z.prototype.f=function(){this.X&&ac(this.X);this.ma&&ac(this.ma);Z.d.f.call(this)}; +Z.prototype.V=function(a){if(this.Ta)ic(this.Ta,a);else if(a&&this.db)this.Ta=this.db();if(this.ma){ic(this.X,!a);ic(this.ma,a)}else if(this.X){var b=this.X;a?C(b,"goog-zippy-expanded"):hb(b,"goog-zippy-expanded");b=this.X;!a?C(b,"goog-zippy-collapsed"):hb(b,"goog-zippy-collapsed")}this.i=a;this.dispatchEvent(new md("toggle",this,this.i))};Z.prototype.fc=function(a){if(a.keyCode==13||a.keyCode==32){this.V(!this.i);a.preventDefault();a.stopPropagation()}};Z.prototype.ec=function(){this.V(!this.i)}; +var md=function(a,b,c){F.call(this,a,b);this.kc=c};v(md,F);var od=function(a,b){this.kb=[];var c=D(a);c=lb(document,"span","ae-zippy",c);var d=0;for(var f;f=c[d];d++){for(var g=f.parentNode.parentNode.parentNode.nextSibling;g&&g.nodeType!=1;)g=g.nextSibling;this.kb.push(new Z(f,g,k))}this.Lb=new nd(this.kb,D(b))};od.prototype.Mb=function(){return this.Lb};od.prototype.Nb=function(){return this.kb}; +var nd=function(a,b){this.Aa=a;if(this.Aa.length){var c=0;for(var d;d=this.Aa[c];c++)L(d,"toggle",this.jc,k,this)}this.eb=0;this.i=k;c="ae-toggle ae-plus ae-action";this.Aa.length||(c+=" ae-disabled");this.P=pb("span",{className:c},"Expand All");L(this.P,"click",this.Jb,k,this);b.appendChild(this.P)};nd.prototype.Jb=function(){this.Aa.length&&this.V(!this.i)}; +nd.prototype.jc=function(a){a=a.currentTarget;if(a.i)this.eb+=1;else this.eb-=1;if(a.i!=this.i)if(a.i){this.i=h;pd(this,h)}else if(this.eb==0){this.i=k;pd(this,k)}};nd.prototype.V=function(a){this.i=a;a=0;for(var b;b=this.Aa[a];a++)b.i!=this.i&&b.V(this.i);pd(this)}; +var pd=function(a,b){if(b!==undefined?b:a.i){hb(a.P,"ae-plus");C(a.P,"ae-minus");ub(a.P,"Collapse All")}else{hb(a.P,"ae-minus");C(a.P,"ae-plus");ub(a.P,"Expand All")}},qd=function(a){this.ic=a;this.Bb={};var b,c=pb("div",{},b=pb("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),pb("div",{className:"goog-tab-bar-clear"}),a=pb("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new Y;d.K(b);L(d,"select",this.mb,k,this);L(d,"unselect",this.mb,k,this); +b=0;for(var f;f=this.ic[b];b++)if(f=D("ae-stats-details-"+f)){var g=lb(document,"h2",j,f)[0],i;i=g;var l=void 0;if(x&&"innerText"in i)l=i.innerText.replace(/(\r\n|\r|\n)/g,"\n");else{l=[];yb(i,l,h);l=l.join("")}l=l.replace(/\xAD/g,"");l=l.replace(/ +/g," ");if(l!=" ")l=l.replace(/^\s*/,"");i=l;sb(g);g=new id(i);this.Bb[u(g)]=f;d.Ba(g,h);a.appendChild(f);b==0?d.da(g):ic(f,k)}D("bd").appendChild(c)};qd.prototype.mb=function(a){var b=this.Bb[u(a.target)];ic(b,a.type=="select")}; +aa("ae.Stats.Details.Tabs",qd,void 0);aa("goog.ui.Zippy",Z,void 0);Z.prototype.setExpanded=Z.prototype.V;aa("ae.Stats.MakeZippys",od,void 0);od.prototype.getExpandCollapse=od.prototype.Mb;od.prototype.getZippys=od.prototype.Nb;nd.prototype.setExpanded=nd.prototype.V;var $=function(){this.Qa=[];this.fb=[]},rd=[[5,0.2,1],[6,0.2,1.2],[5,0.25,1.25],[6,0.25,1.5],[4,0.5,2],[5,0.5,2.5],[6,0.5,3],[4,1,4],[5,1,5],[6,1,6],[4,2,8],[5,2,10]],sd=function(a){if(a<=0)return[2,0.5,1];for(var b=1;a<1;){a*=10;b/=10}for(;a>=10;){a/=10;b*=10}for(var c=0;c');a.write('
');for(var f=0;f<=b;f++){a.write('');a.write('');a.write(" "+f*c+"")}a.write("
\n")}; +$.prototype.Kb=function(){this.fb=[];var a=sd(this.ab),b=a[0],c=a[1];a=100/a[2];this.write('\n');td(this,b,c,a);for(var d=0;d\n\n")}td(this,b,c,a);this.write("
');if(f.label.length>0){f.ga.length>0&&this.write('');this.write(f.label);f.ga.length>0&&this.write("")}this.write("");this.write('
\n");return this.fb.join("")};$.prototype.Fb=function(a,b,c,d,f,g){this.ab=Math.max(this.ab,Math.max(b+c,b+d));this.Qa.push({label:a,start:b,duration:c,Ua:d,sb:f,ga:g})};aa("Gantt",$,void 0);$.prototype.add_bar=$.prototype.Fb;$.prototype.draw=$.prototype.Kb;})(); diff --git a/google_appengine/google/appengine/ext/appstats/static/gantt.js b/google_appengine/google/appengine/ext/appstats/static/gantt.js new file mode 100644 index 0000000..4c1d651 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/static/gantt.js @@ -0,0 +1,282 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +/** + * Defines a class that can render a simple Gantt chart. + * + * @author guido@google.com (Guido van Rossum) + * @author schefflerjens@google.com (Jens Scheffler) + */ + +/** + * @constructor + */ +var Gantt = function() { + /** + * @type {Array} + */ + this.bars = []; + + /** + * @type {Array} + */ + this.output = []; +}; + + +/** + * Internal fields used to render the chart. + * Should not be modified. + * @type {Array.} + */ +Gantt.SCALES = [[5, 0.2, 1.0], + [6, 0.2, 1.2], + [5, 0.25, 1.25], + [6, 0.25, 1.5], + [4, 0.5, 2.0], + [5, 0.5, 2.5], + [6, 0.5, 3.0], + [4, 1.0, 4.0], + [5, 1.0, 5.0], + [6, 1.0, 6.0], + [4, 2.0, 8.0], + [5, 2.0, 10.0]]; + + +/** + * Helper to compute the proper X axis scale. + * Args: + * highest: the highest value in the data series. + * + * Returns: + * A tuple (howmany, spacing, limit) where howmany is the number of + * increments, spacing is the increment to be used between successive + * axis labels, and limit is the rounded-up highest value of the + * axis. Within float precision, howmany * spacing == highest will + * hold. + * + * The axis is assumed to always start at zero. + */ +Gantt.compute_scale = function(highest) { + if (highest <= 0) { + return [2, 0.5, 1.0] // Special-case if there's no data. + } + var scale = 1.0 + while (highest < 1.0) { + highest *= 10.0 + scale /= 10.0 + } + while (highest >= 10.0) { + highest /= 10.0 + scale *= 10.0 + } + // Now 1 <= highest < 10 + for (var i = 0; i < Gantt.SCALES.length; i++) { + if (highest <= Gantt.SCALES[i][2]) { + return [Gantt.SCALES[i][0], Gantt.SCALES[i][1] * scale, + Gantt.SCALES[i][2] * scale]; + } + } + // Avoid the need for "assert False". Not actually reachable. + return [5, 2.0 * scale, 10.0 * scale]; +}; + + +/** + * URL of a transparent 1x1 GIF. + * @type {string} + */ +Gantt.prototype.PIX = 'stats/static/pix.gif'; + + +/** + * CSS class name prefix. + * @type {string} + */ +Gantt.prototype.PREFIX = 'ae-stats-gantt-'; + + +/** + * Height of one bar. + * @type {string} + */ +Gantt.prototype.HEIGHT = '1em'; + + +/** + * Height of the extra bar. + * @type {string} + */ +Gantt.prototype.EXTRA_HEIGHT = '0.5em'; + + +/** + * Background color for the bar. + * @type {string} + */ +Gantt.prototype.BG_COLOR = '#eeeeff'; + + +/** + * Color of the main bar. + * @type {string} + */ +Gantt.prototype.COLOR = '#7777ff'; + + +/** + * Color of the extra bar. + * @type {string} + */ +Gantt.prototype.EXTRA_COLOR = '#ff6666'; + + +/** + * Font size of inline_label. + * @type {string} + */ +Gantt.prototype.INLINE_FONT_SIZE = '80%'; + + +/** + * Top of inline label text. + * @type {string} + */ +Gantt.prototype.INLINE_TOP = '0.1em'; + + +/** + * Color for ticks. + * @type {string} + */ +Gantt.prototype.TICK_COLOR = 'grey'; + + +/** + * @type {number} + */ +Gantt.prototype.highest_duration = 0; + + +/* + * Appends text to the output array. + * @param {string} text The text to append to the output. + */ +Gantt.prototype.write = function(text) { + this.output.push(text); +}; + + +/* + * Internal helper to draw a table row showing the scale. + * @param {number} howmany + * @param {number} spacing + * @param {number} scale + */ +Gantt.prototype.draw_scale = function(howmany, spacing, scale) { + this.write('' + + ''); + this.write('
'); + for (var i = 0; i <= howmany; i++) { + this.write(''); + this.write(''); + this.write(' ' + (i * spacing) + ''); // TODO: number format %4g + } + this.write('
\n'); +}; + + +/** + * Draw the bar chart as HTML. + */ +Gantt.prototype.draw = function() { + this.output = []; + var scale = Gantt.compute_scale(this.highest_duration); + var howmany = scale[0]; + var spacing = scale[1]; + var limit = scale[2]; + scale = 100.0 / limit; + this.write('\n'); + this.draw_scale(howmany, spacing, scale); + for (var i = 0; i < this.bars.length; i++) { + var bar = this.bars[i]; + this.write('\n\n'); + + } + this.draw_scale(howmany, spacing, scale); + this.write('
'); + if (bar.label.length > 0) { + if (bar.link_target.length > 0) { + this.write(''); + } + this.write(bar.label); + if (bar.link_target.length > 0) { + this.write(''); + } + } + this.write(''); + this.write('
\n'); + + var html = this.output.join(''); + return html; +}; + + +/** + * Add a bar to the chart. + * All arguments representing times or durations should be integers + * or floats expressed in seconds. The scale drawn is always + * expressed in seconds (with limited precision). + * @param {string} label Valid HTML or HTML-escaped text for the left column. + * @param {number} start Start time for the event. + * @param {number} duration Duration for the event. + * @param {number} extra_duration Duration for the second bar; use 0 to + * suppress. + * @param {string} inline_label Valid HTML or HTML-escaped text drawn after the + * bars; use '' to suppress. + * @param {string} link_target HTML-escaped link where clicking on any element + * will take you; use '' for no linking. + */ +Gantt.prototype.add_bar = function(label, start, duration, extra_duration, + inline_label, link_target) { + this.highest_duration = Math.max( + this.highest_duration, Math.max(start + duration, + start + extra_duration)); + this.bars.push({label: label, start: start, duration: duration, + extra_duration: extra_duration, inline_label: inline_label, + link_target: link_target}); +}; + + +goog.exportSymbol('Gantt', Gantt); +goog.exportProperty(Gantt.prototype, 'add_bar', Gantt.prototype.add_bar); +goog.exportProperty(Gantt.prototype, 'draw', Gantt.prototype.draw); diff --git a/google_appengine/google/appengine/ext/appstats/static/minus.gif b/google_appengine/google/appengine/ext/appstats/static/minus.gif new file mode 100644 index 0000000000000000000000000000000000000000..f95122d0fcf0d3de4cd7a090f356d0f6cf9c2763 GIT binary patch literal 199 zcwPa=0670gNk%w1VGIBa0K^{v&(YZN^Y_Wj)9>;1+1ugQ+23$~y6*7w*xKOC&ei<< z{Qdp?^Y!=O;^+AI_~qy8*4f_Z>Fwz0?%dzy%g)vC@$>ii`qkLo;^gS;?(pa7?VF{~ zRc5UK0L=gY|NsC0A^8LW0018VEC2ui01N;O000F<;J0yPNp2$Nt?bGfYAcg+yU?|# zIHcA6z+Z*L2nI8x!(bU`9x@S+SLA>F(_A@Z#j?;Ns`m+u`%| z_vq>E@bUBb`1s4t)%W=N{r&yc+1};n>)hYv%+A%%(b(14-S6@B@$>iR>FvqP)0?Hw z007MY|Nm8Ht^fc3A^8LW0018VEC2ui01N;O000F@;J0~XNp7Oot?cRrD$8*#+0HCv z%X9}dq{nPHWRMg@qJadsU@$WbfUqECu7z1fazKzK34vfXh*$=mQN^KYw<)ibse}pv F06Vqjb{+r# literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/appstats/templates/base.html b/google_appengine/google/appengine/ext/appstats/templates/base.html new file mode 100644 index 0000000..b1abe9d --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/templates/base.html @@ -0,0 +1,39 @@ + + + + + + Appstats - {{env.APPLICATION_ID}} + {% block headstuff %}{% endblock %} + + +
+ {# Header begin #} +
+
+ +
+
+

Application Stats for {{env.APPLICATION_ID}}

+
+
+ {# Header end #} + {# Body begin #} +
+ {# Content begin #} +
+ {% block content %} + {# REAL BODY GOES HERE #} + {% endblock %} +
+ {# Content end #} +
+ {# Body end #} +
+ +{% block tailstuff %}{% endblock %} + + diff --git a/google_appengine/google/appengine/ext/appstats/templates/details.html b/google_appengine/google/appengine/ext/appstats/templates/details.html new file mode 100644 index 0000000..20710af --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/templates/details.html @@ -0,0 +1,214 @@ +{% extends "base.html" %} +{% block headstuff %}{% endblock %} +{% block content %} + {% if not record %} +

Invalid or stale record key!

+ {% else %} +
+
+
+ {{record.start_time_formatted}}
+ + {{record.http_status}} + +
+
+ + {{record.http_method|escape}} {{record.http_path|escape}}{{record.http_query|escape}} + +
+ {{record.user_email|escape}}{% if record.is_admin %}*{% endif %} + real={{record.duration_milliseconds}}ms + cpu={{record.processor_milliseconds}}ms + api={{record.api_milliseconds}}ms + overhead={{record.overhead_walltime_milliseconds}}ms +
+
+
+ + +
+

Timeline

+
+
[Chart goes here]
+
+ {% if record.individual_stats_size %} +
+
+
+

RPC Call Traces

+
+
+
+ + + + + + + {% for t in record.individual_stats_list %} + + + + + + + {% if t.request_data_summary %} + + + + {% endif %} + {% if t.response_data_summary %} + + + + {% endif %} + {% if t.call_stack_size %} + + + + {% for f in t.call_stack_list %} + + + + {% if f.variables_size %} + + + + {% endif %}{# f.variables_size #} + {% endfor %}{# t.call_stack_list #} + {% endif %}{# t.call_stack_size #} + + {% endfor %}{# record.individual_stats_list #} +
RPC
+ + @{{t.start_offset_milliseconds}}ms + {{t.service_call_name|escape}} + real={{t.duration_milliseconds}}ms + api={{t.api_milliseconds}}ms +
Request: {{t.request_data_summary|escape}}
Response: {{t.response_data_summary|escape}}
Stack:
+   + {% if file_url %}{% endif %} {{f.class_or_file_name|escape}}:{{f.line_number}}{% if file_url %}{% endif %} {{f.function_name|escape}}() +
{% for item in f.variables_list %}{{item.key|escape}} = {{item.value|escape}}
{% endfor %} +
+
+ {% endif %}{# traces #} + {% endif %} +
+ + {% if rpcstats_by_count %} +
+

RPC Stats

+ + + + + + + + + {% for item in rpcstats_by_count %} + + + + + + + {% endfor %} + +
service.call#RPCsreal timeapi time
{{item.0|escape}}{{item.1|escape}}{{item.2}}ms{{item.3}}ms
+
+ {% endif %}{# rpcstats_by_count #} + + {% if record.cgi_env_size %} +
+

CGI Environment

+ + + {% for item in record.cgi_env_list %} + + + + + {% endfor %} + +
{{item.key|escape}}={{item.value|escape}}
+
+ {% endif %}{# record.cgi_env_size #} + + {% if sys.path %} +
+

sys.path

+ + + + + + + + {% for item in sys.path %} + + + + + {% endfor %} + +
+ Note: sys.path is not saved with the request; + this is the current sys.path. +
{{forloop.counter0}}:{{item|escape}}
+
+ {% endif %}{# sys.path #} + +{% endblock %} + +{% block tailstuff %} + + + +{% endblock %} diff --git a/google_appengine/google/appengine/ext/appstats/templates/file.html b/google_appengine/google/appengine/ext/appstats/templates/file.html new file mode 100644 index 0000000..3496bb8 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/templates/file.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block content %} + +

{{orig_filename|escape}}

+Go to line {{lineno}} | +Go to bottom + +
+{% for line in fp %}{{forloop.counter|rjust:"4"}}: {{line|escape}}{% endfor %}
+
+Back to top +
+ +{% endblock %} diff --git a/google_appengine/google/appengine/ext/appstats/templates/main.html b/google_appengine/google/appengine/ext/appstats/templates/main.html new file mode 100644 index 0000000..ab3c39f --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/templates/main.html @@ -0,0 +1,166 @@ +{% extends "base.html" %} + +{% block content %} + +
+ {% comment %} +
+ {% endcomment %} + +
+
+
+ {# RPC stats table begin #} +
+
+
+

RPC Stats

+
+
+
+ + + + + + + + + + + + {% for item in allstats_by_count %} + + + + + + + + {% for subitem in item.2 %} + + + + + {% endfor %} + + {% endfor %} +
RPCCount
+ + {{item.0|escape}} + {{item.1}}
{{subitem.0|escape}}{{subitem.1}}
+
+ {# RPC stats table end #} +
+
+ {# Path stats table begin #} +
+
+
+

Path Stats

+
+
+
+ + + + + + + + + + + + + + + + {% for item in pathstats_by_count %} + + + + + + + {% for subitem in item.4 %} + + + + + + {% endfor %} + + {% endfor %} +
Path#RPCs#RequestsMost Recent requests
+ + {{item.0|escape}} + + {{item.1}} + {{item.2}} + {% for index in item.3 %} + {% if index %} ({{index}}) {% else %} ... {% endif %} + {% endfor %} +
{{subitem.0|escape}}{{subitem.1|escape}}
+
+ {# Path stats table end #} +
+
+
+
+
+

Requests History

+
+
+
+ + + + + + + + + + + {% for r in requests %} + + + + + + + {% for item in r.rpc_stats_list %} + + + + + {% endfor %} + + {% endfor %} +
Request
+ + ({{forloop.counter}}) + + {{r.start_time_formatted}} + "{{r.http_method|escape}} + {{r.http_path|escape}}{{r.http_query|escape}}" + {{r.http_status}} + + real={{r.duration_milliseconds}}ms + cpu={{r.processor_milliseconds}}ms + api={{r.api_milliseconds}}ms + overhead={{r.overhead_walltime_milliseconds}}ms + ({{r.combined_rpc_count}} RPC{{r.combined_rpc_count|pluralize}}) +
{{item.service_call_name}}{{item.total_amount_of_calls}}
+
+ +{% endblock %} + +{% block tailstuff %} + +{% endblock %} diff --git a/google_appengine/google/appengine/ext/appstats/templates/test.html b/google_appengine/google/appengine/ext/appstats/templates/test.html new file mode 100644 index 0000000..61f59c7 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/templates/test.html @@ -0,0 +1,2 @@ +{{foo}} +{{env.BAR}} diff --git a/google_appengine/google/appengine/ext/appstats/ui.py b/google_appengine/google/appengine/ext/appstats/ui.py new file mode 100755 index 0000000..da44936 --- /dev/null +++ b/google_appengine/google/appengine/ext/appstats/ui.py @@ -0,0 +1,288 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Web-based User Interface for appstats. + +This is a simple set of webapp-based request handlers that display the +collected statistics and let you drill down on the information in +various ways. + +Template files are in the templates/ subdirectory. Static files are +in the static/ subdirectory. + +The templates are written to work with either Django 0.96 or Django +1.0, and most likely they also work with Django 1.1. +""" + + +import cgi +import email.Utils +import logging +import mimetypes +import os +import re +import sys +import time + +from google.appengine.api import users +from google.appengine.ext import webapp +from google.appengine.ext.webapp import util + +from google.appengine.ext.appstats import recording + +DEBUG = recording.config.DEBUG +from google.appengine.ext.webapp import template +import django + + +def render(tmplname, data): + """Helper function to render a template.""" + here = os.path.dirname(__file__) + tmpl = os.path.join(here, 'templates', tmplname) + data['env'] = os.environ + try: + return template.render(tmpl, data) + except Exception, err: + logging.exception('Failed to render %s', tmpl) + return 'Problematic template %s: %s' % (tmplname, err) + + +class SummaryHandler(webapp.RequestHandler): + """Request handler for the main stats page (/stats/).""" + + def get(self): + recording.dont_record() + + if not self.request.path.endswith('stats/'): + self.redirect('stats/') + return + + summaries = recording.load_summary_protos() + + allstats = {} + pathstats = {} + pivot_path_rpc = {} + pivot_rpc_path = {} + for index, summary in enumerate(summaries): + path_key = recording.config.extract_key(summary) + if path_key not in pathstats: + pathstats[path_key] = [1, index+1] + else: + values = pathstats[path_key] + values[0] += 1 + if len(values) >= 11: + if values[-1]: + values.append(0) + else: + values.append(index+1) + if path_key not in pivot_path_rpc: + pivot_path_rpc[path_key] = {} + for x in summary.rpc_stats_list(): + rpc_key = x.service_call_name() + value = x.total_amount_of_calls() + if rpc_key in allstats: + allstats[rpc_key] += value + else: + allstats[rpc_key] = value + if rpc_key not in pivot_path_rpc[path_key]: + pivot_path_rpc[path_key][rpc_key] = 0 + pivot_path_rpc[path_key][rpc_key] += value + if rpc_key not in pivot_rpc_path: + pivot_rpc_path[rpc_key] = {} + if path_key not in pivot_rpc_path[rpc_key]: + pivot_rpc_path[rpc_key][path_key] = 0 + pivot_rpc_path[rpc_key][path_key] += value + + allstats_by_count = [] + for k, v in allstats.iteritems(): + pivot = sorted(pivot_rpc_path[k].iteritems(), + key=lambda x: (-x[1], x[0])) + allstats_by_count.append((k, v, pivot)) + allstats_by_count.sort(key=lambda x: (-x[1], x[0])) + + pathstats_by_count = [] + for path_key, values in pathstats.iteritems(): + pivot = sorted(pivot_path_rpc[path_key].iteritems(), + key=lambda x: (-x[1], x[0])) + rpc_count = sum(x[1] for x in pivot) + pathstats_by_count.append((path_key, rpc_count, + values[0], values[1:], pivot)) + pathstats_by_count.sort(key=lambda x: (-x[1], -x[2], x[0])) + + data = {'requests': summaries, + 'allstats_by_count': allstats_by_count, + 'pathstats_by_count': pathstats_by_count, + } + self.response.out.write(render('main.html', data)) + + +class DetailsHandler(webapp.RequestHandler): + """Request handler for the details page (/stats/details).""" + + def get(self): + recording.dont_record() + + time_key = self.request.get('time') + timestamp = None + record = None + if time_key: + try: + timestamp = int(time_key) * 0.001 + except Exception: + pass + if timestamp: + record = recording.load_full_proto(timestamp) + + if record is None: + self.response.set_status(404) + self.response.out.write(render('details.html', {})) + return + + rpcstats_map = {} + for rpc_stat in record.individual_stats_list(): + key = rpc_stat.service_call_name() + count, real, api = rpcstats_map.get(key, (0, 0, 0)) + count += 1 + real += rpc_stat.duration_milliseconds() + api += rpc_stat.api_mcycles() + rpcstats_map[key] = (count, real, api) + rpcstats_by_count = [ + (name, count, real, recording.mcycles_to_msecs(api)) + for name, (count, real, api) in rpcstats_map.iteritems()] + rpcstats_by_count.sort(key=lambda x: -x[1]) + + real_total = 0 + api_total_mcycles = 0 + for i, rpc_stat in enumerate(record.individual_stats_list()): + real_total += rpc_stat.duration_milliseconds() + api_total_mcycles += rpc_stat.api_mcycles() + + api_total = recording.mcycles_to_msecs(api_total_mcycles) + charged_total = recording.mcycles_to_msecs(record.processor_mcycles() + + api_total_mcycles) + + data = {'sys': sys, + 'record': record, + 'rpcstats_by_count': rpcstats_by_count, + 'real_total': real_total, + 'api_total': api_total, + 'charged_total': charged_total, + 'file_url': './file', + } + self.response.out.write(render('details.html', data)) + + +class FileHandler(webapp.RequestHandler): + """Request handler for displaying any text file in the system. + + NOTE: This gives any admin of your app full access to your source code. + """ + + + def get(self): + recording.dont_record() + lineno = self.request.get('n') + try: + lineno = int(lineno) + except: + lineno = 0 + filename = self.request.get('f') or '' + orig_filename = filename + match = re.match('(.*)', filename) + if match: + index, tail = match.groups() + index = int(index) + if index < len(sys.path): + filename = sys.path[index] + tail + try: + fp = open(filename) + except IOError, err: + self.response.out.write('

IOError

%s
' % + cgi.escape(str(err))) + self.response.set_status(404) + else: + try: + data = {'fp': fp, + 'filename': filename, + 'orig_filename': orig_filename, + 'lineno': lineno, + } + self.response.out.write(render('file.html', data)) + finally: + fp.close() + + +class StaticHandler(webapp.RequestHandler): + """Request handler to serve static files. + + Only files directory in the static subdirectory are rendered this + way (no subdirectories). + """ + + def get(self): + here = os.path.dirname(__file__) + fn = self.request.path + i = fn.rfind('/') + fn = fn[i+1:] + fn = os.path.join(here, 'static', fn) + ctype, encoding = mimetypes.guess_type(fn) + assert ctype and '/' in ctype, repr(ctype) + expiry = 3600 + expiration = email.Utils.formatdate(time.time() + expiry, usegmt=True) + fp = open(fn, 'rb') + try: + self.response.out.write(fp.read()) + finally: + fp.close() + self.response.headers['Content-type'] = ctype + self.response.headers['Cache-Control'] = 'public, max-age=expiry' + self.response.headers['Expires'] = expiration + + +if django.VERSION[:2] < (0, 97): + from django.template import defaultfilters + def safe(text, dummy=None): + return text + defaultfilters.register.filter("safe", safe) + + +URLMAP = [ + ('.*/details', DetailsHandler), + ('.*/file', FileHandler), + ('.*/static/.*', StaticHandler), + ('.*', SummaryHandler), + ] + + +def main(): + """Main program. Auth check, then create and run the WSGIApplication.""" + if not os.getenv('SERVER_SOFTWARE', '').startswith('Dev'): + if not users.is_current_user_admin(): + if users.get_current_user() is None: + print 'Status: 302' + print 'Location:', users.create_login_url(os.getenv('PATH_INFO', '')) + else: + print 'Status: 403' + print + print 'Forbidden' + return + app = webapp.WSGIApplication(URLMAP, debug=DEBUG) + util.run_bare_wsgi_app(app) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/ext/blobstore/__init__.py b/google_appengine/google/appengine/ext/blobstore/__init__.py new file mode 100644 index 0000000..0a9f146 --- /dev/null +++ b/google_appengine/google/appengine/ext/blobstore/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Blobstore API module.""" + +from blobstore import * diff --git a/google_appengine/google/appengine/ext/blobstore/blobstore.py b/google_appengine/google/appengine/ext/blobstore/blobstore.py new file mode 100755 index 0000000..480888c --- /dev/null +++ b/google_appengine/google/appengine/ext/blobstore/blobstore.py @@ -0,0 +1,703 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A Python blobstore API used by app developers. + +Contains methods used to interface with Blobstore API. Includes db.Model-like +class representing a reference to a very large BLOB. Imports db.Key-like +class representing a blob-key. +""" + + + +import cgi +import email +import os + +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api.blobstore import blobstore +from google.appengine.ext import db + +__all__ = ['BLOB_INFO_KIND', + 'BLOB_KEY_HEADER', + 'BLOB_RANGE_HEADER', + 'BlobFetchSizeTooLargeError', + 'BlobInfo', + 'BlobInfoParseError', + 'BlobKey', + 'BlobNotFoundError', + 'BlobReferenceProperty', + 'BlobReader', + 'DataIndexOutOfRangeError', + 'Error', + 'InternalError', + 'MAX_BLOB_FETCH_SIZE', + 'UPLOAD_INFO_CREATION_HEADER', + 'create_upload_url', + 'delete', + 'fetch_data', + 'get', + 'parse_blob_info'] + +Error = blobstore.Error +InternalError = blobstore.InternalError +BlobFetchSizeTooLargeError = blobstore.BlobFetchSizeTooLargeError +BlobNotFoundError = blobstore.BlobNotFoundError +_CreationFormatError = blobstore._CreationFormatError +DataIndexOutOfRangeError = blobstore.DataIndexOutOfRangeError + +BlobKey = blobstore.BlobKey +create_upload_url = blobstore.create_upload_url +delete = blobstore.delete + + +class BlobInfoParseError(Error): + """CGI parameter does not contain valid BlobInfo record.""" + + +BLOB_INFO_KIND = blobstore.BLOB_INFO_KIND +BLOB_KEY_HEADER = blobstore.BLOB_KEY_HEADER +BLOB_RANGE_HEADER = blobstore.BLOB_RANGE_HEADER +MAX_BLOB_FETCH_SIZE = blobstore.MAX_BLOB_FETCH_SIZE +UPLOAD_INFO_CREATION_HEADER = blobstore.UPLOAD_INFO_CREATION_HEADER + +class _GqlQuery(db.GqlQuery): + """GqlQuery class that explicitly sets model-class. + + This does the same as the original db.GqlQuery class except that it does + not try to find the model class based on the compiled GQL query. The + caller instead provides the query with a model class to use for construction. + + This class is required for compatibility with the current db.py query + mechanism but will be removed in the future. DO NOT USE. + """ + + def __init__(self, query_string, model_class, *args, **kwds): + """Constructor. + + Args: + query_string: Properly formatted GQL query string. + model_class: Model class from which entities are constructed. + *args: Positional arguments used to bind numeric references in the query. + **kwds: Dictionary-based arguments for named references. + """ + from google.appengine.ext import gql + app = kwds.pop('_app', None) + self._proto_query = gql.GQL(query_string, _app=app, namespace='') + super(db.GqlQuery, self).__init__(model_class, namespace='') + self.bind(*args, **kwds) + + +class BlobInfo(object): + """Information about blobs in Blobstore. + + This is a db.Model-like class that contains information about blobs stored + by an application. Like db.Model, this class is backed by an Datastore + entity, however, BlobInfo instances are read-only and have a much more + limited interface. + + Each BlobInfo has a key of type BlobKey associated with it. This key is + specific to the Blobstore API and is not compatible with db.get. The key + can be used for quick lookup by passing it to BlobInfo.get. This + key converts easily to a string, which is web safe and can be embedded + in URLs. + + Properties: + content_type: Content type of blob. + creation: Creation date of blob, when it was uploaded. + filename: Filename user selected from their machine. + size: Size of uncompressed blob. + + All properties are read-only. Attempting to assign a value to a property + will raise NotImplementedError. + """ + + _unindexed_properties = frozenset() + + @property + def content_type(self): + return self.__get_value('content_type') + + @property + def creation(self): + return self.__get_value('creation') + + @property + def filename(self): + return self.__get_value('filename') + + @property + def size(self): + return self.__get_value('size') + + def __init__(self, entity_or_blob_key, _values=None): + """Constructor for wrapping blobstore entity. + + The constructor should not be used outside this package and tests. + + Args: + entity: Datastore entity that represents the blob reference. + """ + if isinstance(entity_or_blob_key, datastore.Entity): + self.__entity = entity_or_blob_key + self.__key = BlobKey(entity_or_blob_key.key().name()) + elif isinstance(entity_or_blob_key, BlobKey): + self.__entity = _values + self.__key = entity_or_blob_key + else: + TypeError('Must provide Entity or BlobKey') + + @classmethod + def from_entity(cls, entity): + """Convert entity to BlobInfo. + + This method is required for compatibility with the current db.py query + mechanism but will be removed in the future. DO NOT USE. + """ + return BlobInfo(entity) + + @classmethod + def properties(cls): + """Set of properties that belong to BlobInfo. + + This method is required for compatibility with the current db.py query + mechanism but will be removed in the future. DO NOT USE. + """ + return set(('content_type', 'creation', 'filename', 'size')) + + def __get_value(self, name): + """Get a BlobInfo value, loading entity if necessary. + + This method allows lazy loading of the underlying datastore entity. It + should never be invoked directly. + + Args: + name: Name of property to get value for. + + Returns: + Value of BlobInfo property from entity. + """ + if self.__entity is None: + self.__entity = datastore.Get( + datastore_types.Key.from_path( + self.kind(), str(self.__key), namespace='')) + try: + return self.__entity[name] + except KeyError: + raise AttributeError(name) + + + def key(self): + """Get key for blob. + + Returns: + BlobKey instance that identifies this blob. + """ + return self.__key + + def delete(self): + """Permanently delete blob from Blobstore.""" + delete(self.key()) + + @classmethod + def get(cls, blob_keys): + """Retrieve BlobInfo by key or list of keys. + + Args: + blob_keys: A key or a list of keys. Keys may be instances of str, + unicode and BlobKey. + + Returns: + A BlobInfo instance associated with provided key or a list of BlobInfo + instances if a list of keys was provided. Keys that are not found in + Blobstore return None as their values. + """ + blob_keys = cls.__normalize_and_convert_keys(blob_keys) + try: + entities = datastore.Get(blob_keys) + except datastore_errors.EntityNotFoundError: + return None + if isinstance(entities, datastore.Entity): + return BlobInfo(entities) + else: + references = [] + for entity in entities: + if entity is not None: + references.append(BlobInfo(entity)) + else: + references.append(None) + return references + + @classmethod + def all(cls): + """Get query for all Blobs associated with application. + + Returns: + A db.Query object querying over BlobInfo's datastore kind. + """ + return db.Query(model_class=cls, namespace='') + + @classmethod + def __factory_for_kind(cls, kind): + if kind == BLOB_INFO_KIND: + return BlobInfo + raise ValueError('Cannot query for kind %s' % kind) + + @classmethod + def gql(cls, query_string, *args, **kwds): + """Returns a query using GQL query string. + + See appengine/ext/gql for more information about GQL. + + Args: + query_string: Properly formatted GQL query string with the + 'SELECT * FROM ' part omitted + *args: rest of the positional arguments used to bind numeric references + in the query. + **kwds: dictionary-based arguments (for named parameters). + + Returns: + A gql.GqlQuery object querying over BlobInfo's datastore kind. + """ + return _GqlQuery('SELECT * FROM %s %s' + % (cls.kind(), query_string), + cls, + *args, + **kwds) + + @classmethod + def kind(self): + """Get the entity kind for the BlobInfo. + + This method is required for compatibility with the current db.py query + mechanism but will be removed in the future. DO NOT USE. + """ + return BLOB_INFO_KIND + + @classmethod + def __normalize_and_convert_keys(cls, keys): + """Normalize and convert all keys to BlobKey type. + + This method is based on datastore.NormalizeAndTypeCheck(). + + Args: + keys: A single key or a list/tuple of keys. Keys may be a string + or BlobKey + + Returns: + Single key or list with all strings replaced by BlobKey instances. + """ + if isinstance(keys, (list, tuple)): + multiple = True + keys = list(keys) + else: + multiple = False + keys = [keys] + + for index, key in enumerate(keys): + if not isinstance(key, (basestring, BlobKey)): + raise datastore_errors.BadArgumentError( + 'Expected str or BlobKey; received %s (a %s)' % ( + key, + datastore.typename(key))) + keys[index] = datastore.Key.from_path(cls.kind(), str(key), namespace='') + + if multiple: + return keys + else: + return keys[0] + + +def get(blob_key): + """Get a BlobInfo record from blobstore. + + Does the same as BlobInfo.get. + """ + return BlobInfo.get(blob_key) + + +def parse_blob_info(field_storage): + """Parse a BlobInfo record from file upload field_storage. + + Args: + field_storage: cgi.FieldStorage that represents uploaded blob. + + Returns: + BlobInfo record as parsed from the field-storage instance. + None if there was no field_storage. + + Raises: + BlobInfoParseError when provided field_storage does not contain enough + information to construct a BlobInfo object. + """ + if field_storage is None: + return None + + field_name = field_storage.name + + def get_value(dict, name): + value = dict.get(name, None) + if value is None: + raise BlobInfoParseError( + 'Field %s has no %s.' % (field_name, name)) + return value + + filename = get_value(field_storage.disposition_options, 'filename') + blob_key = BlobKey(get_value(field_storage.type_options, 'blob-key')) + + upload_content = email.message_from_file(field_storage.file) + content_type = get_value(upload_content, 'content-type') + size = get_value(upload_content, 'content-length') + creation_string = get_value(upload_content, UPLOAD_INFO_CREATION_HEADER) + + try: + size = int(size) + except (TypeError, ValueError): + raise BlobInfoParseError( + '%s is not a valid value for %s size.' % (size, field_name)) + + try: + creation = blobstore._parse_creation(creation_string, field_name) + except blobstore._CreationFormatError, err: + raise BlobInfoParseError(str(err)) + + return BlobInfo(blob_key, + {'content_type': content_type, + 'creation': creation, + 'filename': filename, + 'size': size, + }) + + +class BlobReferenceProperty(db.Property): + """Property compatible with db.Model classes. + + Add references to blobs to domain models using BlobReferenceProperty: + + class Picture(db.Model): + title = db.StringProperty() + image = blobstore.BlobReferenceProperty() + thumbnail = blobstore.BlobReferenceProperty() + + To find the size of a picture using this model: + + picture = Picture.get(picture_key) + print picture.image.size + + BlobInfo objects are lazily loaded so iterating over models with + for BlobKeys is efficient, the following does not need to hit + Datastore for each image key: + + list_of_untitled_blobs = [] + for picture in Picture.gql("WHERE title=''"): + list_of_untitled_blobs.append(picture.image.key()) + """ + + data_type = BlobInfo + + def get_value_for_datastore(self, model_instance): + """Translate model property to datastore value.""" + blob_info = getattr(model_instance, self.name) + if blob_info is None: + return None + return blob_info.key() + + def make_value_from_datastore(self, value): + """Translate datastore value to BlobInfo.""" + if value is None: + return None + return BlobInfo(value) + + def validate(self, value): + """Validate that assigned value is BlobInfo. + + Automatically converts from strings and BlobKey instances. + """ + if isinstance(value, (basestring)): + value = BlobInfo(BlobKey(value)) + elif isinstance(value, BlobKey): + value = BlobInfo(value) + return super(BlobReferenceProperty, self).validate(value) + + +def fetch_data(blob, start_index, end_index): + """Fetch data for blob. + + Fetches a fragment of a blob up to MAX_BLOB_FETCH_SIZE in length. Attempting + to fetch a fragment that extends beyond the boundaries of the blob will return + the amount of data from start_index until the end of the blob, which will be + a smaller size than requested. Requesting a fragment which is entirely + outside the boundaries of the blob will return empty string. Attempting + to fetch a negative index will raise an exception. + + Args: + blob: BlobInfo, BlobKey, str or unicode representation of BlobKey of + blob to fetch data from. + start_index: Start index of blob data to fetch. May not be negative. + end_index: End index (inclusive) of blob data to fetch. Must be + >= start_index. + + Returns: + str containing partial data of blob. If the indexes are legal but outside + the boundaries of the blob, will return empty string. + + Raises: + TypeError if start_index or end_index are not indexes. Also when blob + is not a string, BlobKey or BlobInfo. + DataIndexOutOfRangeError when start_index < 0 or end_index < start_index. + BlobFetchSizeTooLargeError when request blob fragment is larger than + MAX_BLOB_FETCH_SIZE. + BlobNotFoundError when blob does not exist. + """ + if isinstance(blob, BlobInfo): + blob = blob.key() + return blobstore.fetch_data(blob, start_index, end_index) + + +class BlobReader(object): + """Provides a read-only file-like interface to a blobstore blob.""" + + SEEK_SET = 0 + SEEK_CUR = 1 + SEEK_END = 2 + + def __init__(self, blob_key, buffer_size=131072, position=0): + """Constructor. + + Args: + blob_key: The blob key or string blob key to read from. + buffer_size: The minimum size to fetch chunks of data from blobstore. + position: The initial position in the file. + """ + self.__blob_key = blob_key + self.__buffer_size = buffer_size + self.__buffer = "" + self.__position = position + self.__buffer_position = 0 + self.__eof = False + self.__blob_info = None + + def __iter__(self): + """Returns a file iterator for this BlobReader.""" + return self + + def __getstate__(self): + """Returns the serialized state for this BlobReader.""" + return (self.__blob_key, self.__buffer_size, self.__position) + + def __setstate__(self, state): + """Restores pickled state for this BlobReader.""" + self.__init__(*state) + + def close(self): + """Close the file. + + A closed file cannot be read or written any more. Any operation which + requires that the file be open will raise a ValueError after the file has + been closed. Calling close() more than once is allowed. + """ + self.__blob_key = None + + def flush(self): + raise IOError("BlobReaders are read-only") + + def next(self): + """Returns the next line from the file. + + Returns: + A string, terminted by \n. The last line may not be terminated by \n. + If EOF is reached, an empty string will be returned. + """ + line = self.readline() + if not line: + raise StopIteration + return line + + def __read_from_buffer(self, size): + """Reads at most size bytes from the buffer. + + Args: + size: Number of bytes to read, or negative to read the entire buffer. + Returns: + Tuple (data, size): + data: The bytes read from the buffer. + size: The remaining unread byte count. + """ + if not self.__blob_key: + raise ValueError("File is closed") + + if size < 0: + end_pos = len(self.__buffer) + else: + end_pos = self.__buffer_position + size + data = self.__buffer[self.__buffer_position:end_pos] + + data_length = len(data) + size -= data_length + self.__position += data_length + self.__buffer_position += data_length + + if self.__buffer_position == len(self.__buffer): + self.__buffer = "" + self.__buffer_position = 0 + + return data, size + + def __fill_buffer(self, size=0): + """Fills the internal buffer. + + Args: + size: Number of bytes to read. Will be clamped to + [self.__buffer_size, MAX_BLOB_FETCH_SIZE]. + """ + read_size = min(max(size, self.__buffer_size), MAX_BLOB_FETCH_SIZE) + self.__buffer = fetch_data(self.__blob_key, self.__position, + self.__position + read_size - 1) + self.__buffer_position = 0 + self.__eof = len(self.__buffer) < read_size + + def read(self, size=-1): + """Read at most size bytes from the file. + + Fewer bytes are read if the read hits EOF before obtaining size bytes. + If the size argument is negative or omitted, read all data until EOF is + reached. The bytes are returned as a string object. An empty string is + returned when EOF is encountered immediately. + + Calling read() without a size specified is likely to be dangerous, as it + may read excessive amounts of data. + + Args: + size: Optional. The maximum number of bytes to read. When omitted, read() + returns all remaining data in the file. + + Returns: + The read data, as a string. + """ + data_list = [] + while True: + data, size = self.__read_from_buffer(size) + data_list.append(data) + if size == 0 or self.__eof: + return ''.join(data_list) + self.__fill_buffer(size) + + def readline(self, size=-1): + """Read one entire line from the file. + + A trailing newline character is kept in the string (but may be absent when a + file ends with an incomplete line). If the size argument is present and + non-negative, it is a maximum byte count (including the trailing newline) + and an incomplete line may be returned. An empty string is returned only + when EOF is encountered immediately. + + Args: + size: Optional. The maximum number of bytes to read. + + Returns: + The read data, as a string. + """ + data_list = [] + while True: + if size < 0: + end_pos = len(self.__buffer) + else: + end_pos = self.__buffer_position + size + newline_pos = self.__buffer.find('\n', self.__buffer_position, end_pos) + if newline_pos != -1: + data_list.append( + self.__read_from_buffer(newline_pos + - self.__buffer_position + 1)[0]) + break + else: + data, size = self.__read_from_buffer(size) + data_list.append(data) + if size == 0 or self.__eof: + break + self.__fill_buffer() + return ''.join(data_list) + + def readlines(self, sizehint=None): + """Read until EOF using readline() and return a list of lines thus read. + + If the optional sizehint argument is present, instead of reading up to EOF, + whole lines totalling approximately sizehint bytes (possibly after rounding + up to an internal buffer size) are read. + + Args: + sizehint: A hint as to the maximum number of bytes to read. + + Returns: + A list of strings, each being a single line from the file. + """ + lines = [] + while sizehint is None or sizehint > 0: + line = self.readline() + if sizehint: + sizehint -= len(line) + if not line: + break + lines.append(line) + return lines + + def seek(self, offset, whence=SEEK_SET): + """Set the file's current position, like stdio's fseek(). + + The whence argument is optional and defaults to os.SEEK_SET or 0 (absolute + file positioning); other values are os.SEEK_CUR or 1 (seek relative to the + current position) and os.SEEK_END or 2 (seek relative to the file's end). + + Args: + offset: The relative offset to seek to. + whence: Defines what the offset is relative to. See description for + details. + """ + if whence == BlobReader.SEEK_CUR: + offset = self.__position + offset + elif whence == BlobReader.SEEK_END: + offset = self.blob_info.size + offset + self.__buffer = "" + self.__buffer_position = 0 + self.__position = offset + self.__eof = False + + def tell(self): + """Return the file's current position, like stdio's ftell().""" + return self.__position + + def truncate(self, size): + raise IOError("BlobReaders are read-only") + + def write(self, str): + raise IOError("BlobReaders are read-only") + + def writelines(self, sequence): + raise IOError("BlobReaders are read-only") + + @property + def blob_info(self): + """Returns the BlobInfo for this file.""" + if not self.__blob_info: + self.__blob_info = BlobInfo.get(self.__blob_key) + return self.__blob_info + + @property + def closed(self): + """Returns True if this file is closed, False otherwise.""" + return self.__blob_key is None diff --git a/google_appengine/google/appengine/ext/bulkload/__init__.py b/google_appengine/google/appengine/ext/bulkload/__init__.py new file mode 100755 index 0000000..ecec2ea --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/__init__.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkload package: Helpers for both bulkloader and bulkload_client. + +For complete documentation, see the Tools and Libraries section of the +documentation. + +This package contains two separate systems: + * The historical and deprecated bulkload/bulkload_client server mix-in, + in the 'bulkload.bulkload' module; exposed here for backwards compatability. + * New helpers for the bulkloader client (appengine/tools/bulkloader.py). + Many of these helpers can also run on the server though there is not + (as of January 2010) any support for using them there. +""" + + +import bulkload_deprecated +Validate = bulkload_deprecated.Validate +Loader = bulkload_deprecated.Loader +BulkLoad = bulkload_deprecated.BulkLoad +main = bulkload_deprecated.main + diff --git a/google_appengine/google/appengine/ext/bulkload/__init__.pyc b/google_appengine/google/appengine/ext/bulkload/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10aa551bef50ed25c2e7d69bb70bfce1ea9339d6 GIT binary patch literal 879 zcwSvn+m6#P5OpuRKm<>`f;4a2MI~8yEC?Zl5JF{zM8!j1A~%^$tU8{^c4+&V`~ly< z55U;H1W1(8cyi{<@v(pWdX)TpQY}kye8&8}<DA|J zi&85zK24Gjj4Y|`G|d(2l~$qSe`Npx-33VUPbbr^ z8s$#1n4f0|(XFfAX2je&?OF zYWG~3c>(~+M8#JpWO_=jprxwU_zG~>kz>{dEgp^d~HGMM?`RW?Atr_#`M@)iF3^0Erk8dPFK=GlTtnb?eb%o1fd+CEp!?k3&)L zkrjo1%=y{Y!E=9~>))hS6|a2gTE^|fdboFC>8HGC`DAC|dJn;U;gB`dVrEJ!DSJ;_ w69%}fCaN_SgI|Y2HWYB@&-P_Sp^WlH!7j$huMda#=4qIcaJZa296gBdAMCmmkN^Mx literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.py b/google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.py new file mode 100755 index 0000000..5eeacae --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DEPRECATED mix-in handler for bulk loading data into an application. + +Please use the new bulkloader. +""" + + + + + +import Cookie +import StringIO +import csv +import httplib +import os +import traceback + +import google +import wsgiref.handlers + +from google.appengine.api import datastore +from google.appengine.ext import webapp +from google.appengine.ext.bulkload import constants + + +def Validate(value, type): + """ Checks that value is non-empty and of the right type. + + Raises ValueError if value is None or empty, TypeError if it's not the given + type. + + Args: + value: any value + type: a type or tuple of types + """ + if not value: + raise ValueError('Value should not be empty; received %s.' % value) + elif not isinstance(value, type): + raise TypeError('Expected a %s, but received %s (a %s).' % + (type, value, value.__class__)) + + +class Loader(object): + """A base class for creating datastore entities from input data. + + To add a handler for bulk loading a new entity kind into your datastore, + write a subclass of this class that calls Loader.__init__ from your + class's __init__. + + If you need to run extra code to convert entities from the input + data, create new properties, or otherwise modify the entities before + they're inserted, override HandleEntity. + + See the CreateEntity method for the creation of entities from the + (parsed) input data. + """ + + __loaders = {} + __kind = None + __properties = None + + def __init__(self, kind, properties): + """ Constructor. + + Populates this Loader's kind and properties map. Also registers it with + the bulk loader, so that all you need to do is instantiate your Loader, + and the bulkload handler will automatically use it. + + Args: + kind: a string containing the entity kind that this loader handles + + properties: list of (name, converter) tuples. + + This is used to automatically convert the CSV columns into properties. + The converter should be a function that takes one argument, a string + value from the CSV file, and returns a correctly typed property value + that should be inserted. The tuples in this list should match the + columns in your CSV file, in order. + + For example: + [('name', str), + ('id_number', int), + ('email', datastore_types.Email), + ('user', users.User), + ('birthdate', lambda x: datetime.datetime.fromtimestamp(float(x))), + ('description', datastore_types.Text), + ] + """ + Validate(kind, basestring) + self.__kind = kind + + Validate(properties, list) + for name, fn in properties: + Validate(name, basestring) + assert callable(fn), ( + 'Conversion function %s for property %s is not callable.' % (fn, name)) + + self.__properties = properties + + Loader.__loaders[kind] = self + + + def kind(self): + """ Return the entity kind that this Loader handes. + """ + return self.__kind + + def CreateEntity(self, values, key_name=None): + """ Creates an entity from a list of property values. + + Args: + values: list/tuple of str + key_name: if provided, the name for the (single) resulting Entity + + Returns: + list of datastore.Entity + + The returned entities are populated with the property values from the + argument, converted to native types using the properties map given in + the constructor, and passed through HandleEntity. They're ready to be + inserted. + + Raises: + AssertionError if the number of values doesn't match the number + of properties in the properties map. + """ + Validate(values, (list, tuple)) + assert len(values) == len(self.__properties), ( + 'Expected %d CSV columns, found %d.' % + (len(self.__properties), len(values))) + + entity = datastore.Entity(self.__kind, name=key_name) + for (name, converter), val in zip(self.__properties, values): + if converter is bool and val.lower() in ('0', 'false', 'no'): + val = False + entity[name] = converter(val) + + entities = self.HandleEntity(entity) + + if entities is not None: + if not isinstance(entities, (list, tuple)): + entities = [entities] + + for entity in entities: + if not isinstance(entity, datastore.Entity): + raise TypeError('Expected a datastore.Entity, received %s (a %s).' % + (entity, entity.__class__)) + + return entities + + + def HandleEntity(self, entity): + """ Subclasses can override this to add custom entity conversion code. + + This is called for each entity, after its properties are populated from + CSV but before it is stored. Subclasses can override this to add custom + entity handling code. + + The entity to be inserted should be returned. If multiple entities should + be inserted, return a list of entities. If no entities should be inserted, + return None or []. + + Args: + entity: datastore.Entity + + Returns: + datastore.Entity or list of datastore.Entity + """ + return entity + + + @staticmethod + def RegisteredLoaders(): + """ Returns a list of the Loader instances that have been created. + """ + return dict(Loader.__loaders) + + +class BulkLoad(webapp.RequestHandler): + """A handler for bulk load requests. + + This class contains handlers for the bulkloading process. One for + GET to provide cookie information for the upload script, and one + handler for a POST request to upload the entities. + + In the POST request, the body contains the data representing the + entities' property values. The original format was a sequences of + lines of comma-separated values (and is handled by the Load + method). The current (version 1) format is a binary format described + in the Tools and Libraries section of the documentation, and is + handled by the LoadV1 method). + """ + + def get(self): + """ Handle a GET. Just show an info page. + """ + page = self.InfoPage(self.request.uri) + self.response.out.write(page) + + + def post(self): + """ Handle a POST. Reads CSV data, converts to entities, and stores them. + """ + self.response.headers['Content-Type'] = 'text/plain' + response, output = self.Load(self.request.get(constants.KIND_PARAM), + self.request.get(constants.CSV_PARAM)) + self.response.set_status(response) + self.response.out.write(output) + + + def InfoPage(self, uri): + """ Renders an information page with the POST endpoint and cookie flag. + + Args: + uri: a string containing the request URI + Returns: + A string with the contents of the info page to be displayed + """ + page = """ + + +Bulk Loader +""" + + page += ('The bulk load endpoint is: %s
\n' % + (uri, uri)) + + cookies = os.environ.get('HTTP_COOKIE', None) + if cookies: + cookie = Cookie.BaseCookie(cookies) + for param in ['ACSID', 'dev_appserver_login']: + value = cookie.get(param) + if value: + page += ("Pass this flag to the client: --cookie='%s=%s'\n" % + (param, value.value)) + break + + else: + page += 'No cookie found!\n' + + page += '' + return page + + def IterRows(self, reader): + """ Yields a tuple of a line number and row for each row of the CSV data. + + Args: + reader: a csv reader for the input data. + """ + line_num = 1 + for columns in reader: + yield (line_num, columns) + line_num += 1 + + def LoadEntities(self, iter, loader, key_format=None): + """Generates entities and loads them into the datastore. Returns + a tuple of HTTP code and string reply. + + Args: + iter: an iterator yielding pairs of a line number and row contents. + key_format: a format string to convert a line number into an + entity id. If None, then entity ID's are automatically generated. + """ + entities = [] + output = [] + for line_num, columns in iter: + key_name = None + if key_format is not None: + key_name = key_format % line_num + if columns: + try: + output.append('\nLoading from line %d...' % line_num) + new_entities = loader.CreateEntity(columns, key_name=key_name) + if new_entities: + entities.extend(new_entities) + output.append('done.') + except: + stacktrace = traceback.format_exc() + output.append('error:\n%s' % stacktrace) + return (httplib.BAD_REQUEST, ''.join(output)) + + datastore.Put(entities) + + return (httplib.OK, ''.join(output)) + + def Load(self, kind, data): + """Parses CSV data, uses a Loader to convert to entities, and stores them. + + On error, fails fast. Returns a "bad request" HTTP response code and + includes the traceback in the output. + + Args: + kind: a string containing the entity kind that this loader handles + data: a string containing the CSV data to load + + Returns: + tuple (response code, output) where: + response code: integer HTTP response code to return + output: string containing the HTTP response body + """ + data = data.encode('utf-8') + Validate(kind, basestring) + Validate(data, basestring) + output = [] + + try: + loader = Loader.RegisteredLoaders()[kind] + except KeyError: + output.append('Error: no Loader defined for kind %s.' % kind) + return (httplib.BAD_REQUEST, ''.join(output)) + + buffer = StringIO.StringIO(data) + reader = csv.reader(buffer, skipinitialspace=True) + + try: + csv.field_size_limit(800000) + except AttributeError: + pass + + return self.LoadEntities(self.IterRows(reader), loader) + + +def main(*loaders): + """Starts bulk upload. + + Raises TypeError if not, at least one Loader instance is given. + + Args: + loaders: One or more Loader instance. + """ + if not loaders: + raise TypeError('Expected at least one argument.') + + for loader in loaders: + if not isinstance(loader, Loader): + raise TypeError('Expected a Loader instance; received %r' % loader) + + application = webapp.WSGIApplication([('.*', BulkLoad)]) + wsgiref.handlers.CGIHandler().run(application) + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.pyc b/google_appengine/google/appengine/ext/bulkload/bulkload_deprecated.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24351142a30fdac6cae8864fe719409d93ad8f4d GIT binary patch literal 12820 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 transformation. + + The bulkloader will call generate_records and create_entity, and + we'll delegate those to the passed in methods. + """ + + def __init__(self, import_record_iterator, dict_to_entity, name): + """Constructor. + + Args: + import_record_iterator: Method which yields neutral dictionaries. + dict_to_entity: Method dict_to_entity(input_dict) returns model or entity + instance(s). + name: Name to register with the bulkloader importers (as 'kind'). + """ + self.import_record_iterator = import_record_iterator + self.dict_to_entity = dict_to_entity + self.kind = name + self.bulkload_state = BulkloadState() + + def get_high_ids(self): + """Required as part of the bulkloader Loader interface. + + At the moment, this is not actually used by the bulkloader for import, as + import does not currently support specifying numeric ids for keys. + (Unspecified keys will become autogenerated ids.) + + Returns: + dict {ancestor_path : {kind : id}} of high id values, curently always {}. + """ + return {} + + def initialize(self, filename, loader_opts): + """Performs initialization. Merely records the values for later use. + + Args: + filename: The string given as the --filename flag argument. + loader_opts: The string given as the --loader_opts flag argument. + """ + + self.bulkload_state.loader_opts = loader_opts + self.bulkload_state.filename = filename + + def finalize(self): + """Performs finalization actions after the upload completes.""" + pass + + def generate_records(self, filename): + """Iterator yielding neutral dictionaries from the connector object. + + Args: + filename: Filename argument passed in on the command line. + + Returns: + Iterator yielding neutral dictionaries, later passed to create_entity. + """ + return self.import_record_iterator(filename, self.bulkload_state) + + def generate_key(self, line_number, unused_values): + """Bulkloader method to generate keys, mostly unused here. + + This is called by the bulkloader just before it calls create_entity. The + line_number is returned to be passed to the record dict, but otherwise + unused. + + Args: + line_number: Record number from the bulkloader. + unused_values: Neutral dict from generate_records; unused. + + Returns: + line_number for use later on. + """ + return line_number + + def create_entity(self, values, key_name=None, parent=None): + """Creates entity/entities from input values via the dict_to_entity method. + + Args: + values: Neutral dict from generate_records. + key_name: record number from generate_key. + parent: Always None in this implementation of a Loader. + + Returns: + Entity or model instance, or collection of entity or model instances, + to be uploaded. + """ + + input_dict = values + input_dict['__record_number__'] = key_name + return self.dict_to_entity(input_dict, self.bulkload_state) + + +class GenericExporter(object): + """Implements bulkloader.Exporter interface and delegates. + + This will delegate to the passed in entity_to_dict method and the + methods on the export_recorder which are in the ConnectorInterface. + """ + + def __init__(self, export_recorder, entity_to_dict, kind, + sort_key_from_entity): + """Constructor. + + Args: + export_recorder: Object which writes results, an implementation of + ConnectorInterface. + entity_to_dict: Method which converts a single entity to a neutral dict. + kind: Kind to identify this object to the bulkloader. + sort_key_from_entity: Optional method to return a sort key for each + entity. This key will be used to sort the downloaded entities before + passing them to eneity_to_dict. + """ + self.export_recorder = export_recorder + self.entity_to_dict = entity_to_dict + self.kind = kind + self.sort_key_from_entity = sort_key_from_entity + self.calculate_sort_key_from_entity = bool(sort_key_from_entity) + self.bulkload_state = BulkloadState() + + def initialize(self, filename, exporter_opts): + """Performs initialization and validation of the output file. + + Args: + filename: The string given as the --filename flag argument. + exporter_opts: The string given as the --exporter_opts flag argument. + """ + self.bulkload_state.filename = filename + self.bulkload_state.exporter_opts = exporter_opts + self.export_recorder.initialize_export(filename, self.bulkload_state) + + def output_entities(self, entity_iterator): + """Outputs the downloaded entities. + + Args: + entity_iterator: An iterator that yields the downloaded entities + in sorted order. + """ + for entity in entity_iterator: + output_dict = self.entity_to_dict(entity, self.bulkload_state) + if output_dict: + self.export_recorder.write_dict(output_dict) + + def finalize(self): + """Performs finalization actions after the download completes.""" + self.export_recorder.finalize_export() + + +def create_transformer_classes(transformer_spec, config_globals): + """Create an importer and exporter class from a transformer spec. + + Args: + transformer_spec: A bulkloader_parser.TransformerEntry. + config_globals: Dict to use to reference globals for code in the config. + + Raises: + InvalidConfig: when the config is invalid. + + Returns: + Tuple, (importer class, exporter class), each which is in turn a wrapper + for the GenericImporter/GenericExporter class using a DictConvertor object + configured as per the transformer_spec. + """ + if transformer_spec.connector in CONNECTOR_FACTORIES: + connector_factory = CONNECTOR_FACTORIES[transformer_spec.connector] + elif config_globals and '.' in transformer_spec.connector: + try: + connector_factory = eval(transformer_spec.connector, config_globals) + except (NameError, AttributeError): + raise bulkloader_errors.InvalidConfiguration( + 'Invalid connector specified for name=%s. Could not evaluate %s.' % + (transformer_spec.name, transformer_spec.connector)) + else: + raise bulkloader_errors.InvalidConfiguration( + 'Invalid connector specified for name=%s. Must be either a built in ' + 'connector ("%s") or a factory method in a module imported via ' + 'python_preamble.' % + (transformer_spec.name, '", "'.join(CONNECTOR_FACTORIES))) + options = {} + if transformer_spec.connector_options: + options = transformer_spec.connector_options.ToDict() + + try: + connector_object = connector_factory(options, transformer_spec.name) + except TypeError: + raise bulkloader_errors.InvalidConfiguration( + 'Invalid connector specified for name=%s. Could not initialize %s.' % + (transformer_spec.name, transformer_spec.connector)) + + + dict_to_model_object = DictConvertor(transformer_spec) + + class ImporterClass(GenericImporter): + """Class to pass to the bulkloader, wraps the specificed configuration.""" + + def __init__(self): + super(self.__class__, self).__init__( + connector_object.generate_import_record, + dict_to_model_object.dict_to_entity, + transformer_spec.name) + importer_class = ImporterClass + + class ExporterClass(GenericExporter): + """Class to pass to the bulkloader, wraps the specificed configuration.""" + + def __init__(self): + super(self.__class__, self).__init__( + connector_object, + dict_to_model_object.entity_to_dict, + transformer_spec.kind, + transformer_spec.sort_key_from_entity) + exporter_class = ExporterClass + + return importer_class, exporter_class + + +def load_config_from_stream(stream): + """Parse a bulkloader.yaml file into bulkloader loader classes. + + Args: + stream: A stream containing bulkloader.yaml data. + + Returns: + importer_classes, exporter_classes: Constructors suitable to pass to the + bulkloader. + """ + config_globals = {} + config = bulkloader_parser.load_config(stream, config_globals) + importer_classes = [] + exporter_classes = [] + for transformer in config.transformers: + importer, exporter = create_transformer_classes(transformer, config_globals) + if importer: + importer_classes.append(importer) + if exporter: + exporter_classes.append(exporter) + + return importer_classes, exporter_classes + + +def load_config(filename, update_path=True): + """Load a configuration file and create importer and exporter classes. + + Args: + filename: Filename of bulkloader.yaml. + update_path: Should sys.path be extended to include the path of filename? + + Returns: + Tuple, (importer classes, exporter classes) based on the transformers + specified in the file. + """ + + if update_path: + sys.path.append(os.path.abspath(os.path.dirname(os.path.abspath(filename)))) + stream = file(filename, 'r') + try: + return load_config_from_stream(stream) + finally: + stream.close() diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_config.pyc b/google_appengine/google/appengine/ext/bulkload/bulkloader_config.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15c5d944fe39a79c5ac47d68fbbf9b02f35887fc GIT binary patch literal 21320 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 Jv~IL6{V$vi&mjN+ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_errors.py b/google_appengine/google/appengine/ext/bulkload/bulkloader_errors.py new file mode 100755 index 0000000..5de60b1 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/bulkloader_errors.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Exceptions raised by bulkloader methods.""" + + + + +class Error(Exception): + """Base bulkloader error type.""" + + +class ErrorOnTransform(Error): + """An exception was raised during this transform.""" + + +class InvalidConfiguration(Error): + """The configuration is invalid.""" + + +class InvalidCodeInConfiguration(Error): + """A code or lambda statement in the configuration could not be evaulated.""" + + +class InvalidExportData(Error): + """The export data cannot be written using this connector object.""" + + +class InvalidImportData(Error): + """The import data is inconsistent with the configuration.""" diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_errors.pyc b/google_appengine/google/appengine/ext/bulkload/bulkloader_errors.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a1a41e67cac166fcdb8e60547d0b030b0678b90 GIT binary patch literal 2012 zcwWtw%We}f6ox&yH?#;40*QjK#DYjjnI#)kQD}uCA&}B;+RexrpCqO;_Q>`$$r{$Y z8*jif@B|z?nG2UiU{NKJPvSrJ@%g{lo^L-6`rjY(_c7VLn%G`rGhZ=qLM|}|1OhS* z2yc)tgnTCC;<7<_lR(3(tx|0g-Y(QmskR937HY3l+l22GYQI!Fgx@OE{Zj1`en8F! zw_RNW45!N&GAoocR5M|K)5(fX^5iN}jDw~r*tz0nXxnbE)=HQD0&H(w9+wQzU*KIf z$oOpFelv$|05ed}2QCG*s|;hE--|1 zl|m?E%~W`z7Sb(Y!kJPtoXxTf2!MzLk!1~LV=ntSV^1bTF4o-b7810Qm@gB-Q=;WVBGx0{7n#OVfMos zf&2I|lGiK|{H>BxG0Qb`Ubd(31jG3pXk4F8(Y9Ep-2u~Xk3X%MepENjVI=?4`a2BA zXq*GZ&7ZJz!WlJ|Sx7-z1c3kl?_gq;C!9)U=>#ZTvpm7!y8x+!*`L)Q-K`*3B>tSBbt8;*duAc*{Lfvp11#Kq{-R>>VbCk;uzRHTyzzIezh?Zg^V`aU^ciln XX8#O&wSMXx-E+(ByV~0iTE+V(=gs+{ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_parser.py b/google_appengine/google/appengine/ext/bulkload/bulkloader_parser.py new file mode 100755 index 0000000..8256e1f --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/bulkloader_parser.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkloader Config Parser and runner. + +A library to read bulkloader yaml configs. Returns a BulkloaderEntry object +which describes the bulkloader.yaml in object form, including some additional +parsing of things like Python lambdas. +""" + + + + +import inspect +import sys + +from google.appengine.api import validation +from google.appengine.api import yaml_builder +from google.appengine.api import yaml_listener +from google.appengine.api import yaml_object + +from google.appengine.ext.bulkload import bulkloader_errors + + +_global_temp_globals = None + + +class EvaluatedCallable(validation.Validator): + """Validates that a string evaluates to a Python callable. + + Calls eval at validation time and stores the results as a ParsedMethod object. + The ParsedMethod object can be used as a string (original value) or callable + (parsed method). It also exposes supports_bulkload_state if the callable has + a kwarg called 'bulkload_state', which is used to determine how to call + the *_transform methods. + """ + + class ParsedMethod(object): + """Wrap the string, the eval'd method, and supports_bulkload_state.""" + + def __init__(self, value, key): + """Initialze internal state. + + Eval the string value and save the result. + + Args: + value: String to compile as a regular expression. + key: The YAML field name. + + Raises: + InvalidCodeInConfiguration: if the code could not be evaluated, or + the evalauted method is not callable. + """ + self.value = value + try: + self.method = eval(value, _global_temp_globals) + except Exception, err: + raise bulkloader_errors.InvalidCodeInConfiguration( + 'Invalid code for %s. Code: "%s". Details: %s' % (key, value, err)) + if not callable(self.method): + raise bulkloader_errors.InvalidCodeInConfiguration( + 'Code for %s did not return a callable. Code: "%s".' % + (key, value)) + + self.supports_bulkload_state = False + try: + argspec = inspect.getargspec(self.method) + if 'bulkload_state' in argspec[0]: + self.supports_bulkload_state = True + except TypeError: + pass + + def __str__(self): + """Return a string representation of the method: the original string.""" + return self.value + + def __call__(self, *args, **kwargs): + """Call the method.""" + return self.method(*args, **kwargs) + + def __init__(self): + """Initialize EvaluatedCallable validator.""" + super(EvaluatedCallable, self).__init__() + + def Validate(self, value, key): + """Validates that the string compiles as a Python callable. + + Args: + value: String to compile as a regular expression. + key: The YAML field name. + + Returns: + Value wrapped in an object with properties 'value' and 'fn'. + + Raises: + InvalidCodeInConfiguration when value does not compile. + """ + if isinstance(value, self.ParsedMethod): + return value + else: + return self.ParsedMethod(value, key) + + def ToValue(self, value): + """Returns the code string for this value.""" + return value.value + + +OPTIONAL_EVALUATED_CALLABLE = validation.Optional(EvaluatedCallable()) + + +class ConnectorSubOptions(validation.Validated): + """Connector options.""" + + ATTRIBUTES = { + 'delimiter': validation.Optional(validation.TYPE_STR), + 'dialect': validation.Optional(validation.TYPE_STR), + } + + +class ConnectorOptions(validation.Validated): + """Connector options.""" + + ATTRIBUTES = { + 'column_list': + validation.Optional(validation.Repeated(validation.TYPE_STR)), + 'columns': validation.Optional(validation.TYPE_STR), + 'encoding': validation.Optional(validation.TYPE_STR), + 'epilog': validation.Optional(validation.TYPE_STR), + 'export_options': validation.Optional(ConnectorSubOptions), + 'import_options': validation.Optional(ConnectorSubOptions), + 'mode': validation.Optional(validation.TYPE_STR), + 'prolog': validation.Optional(validation.TYPE_STR), + 'style': validation.Optional(validation.TYPE_STR), + 'template': validation.Optional(validation.TYPE_STR), + 'xpath_to_nodes': validation.Optional(validation.TYPE_STR), + 'print_export_header_row': validation.Optional(validation.TYPE_BOOL), + 'skip_import_header_row': validation.Optional(validation.TYPE_BOOL), + } + + def CheckInitialized(self): + """Post-loading 'validation'. Really used to fix up yaml hackyness.""" + super(ConnectorOptions, self).CheckInitialized() + + if self.column_list: + self.column_list = [str(column) for column in self.column_list] + + +class ExportEntry(validation.Validated): + """Describes the optional export transform for a single property.""" + + ATTRIBUTES = { + 'external_name': validation.Optional(validation.TYPE_STR), + 'export_transform': OPTIONAL_EVALUATED_CALLABLE, + } + + +class PropertyEntry(validation.Validated): + """Describes the transform for a single property.""" + + ATTRIBUTES = { + 'property': validation.Type(str), + 'import_transform': OPTIONAL_EVALUATED_CALLABLE, + 'import_template': validation.Optional(validation.TYPE_STR), + 'default_value': validation.Optional(validation.TYPE_STR), + 'export': validation.Optional(validation.Repeated(ExportEntry)), + } + ATTRIBUTES.update(ExportEntry.ATTRIBUTES) + + def CheckInitialized(self): + """Check that all required (combinations) of fields are set. + + Also fills in computed properties. + + Raises: + InvalidConfiguration: If the config is invalid. + """ + super(PropertyEntry, self).CheckInitialized() + + if not (self.external_name or self.import_template or self.export): + raise bulkloader_errors.InvalidConfiguration( + 'Neither external_name nor import_template nor export specified for ' + 'property %s.' % self.property) + + +class TransformerEntry(validation.Validated): + """Describes the transform for an entity (or model) kind.""" + + ATTRIBUTES = { + 'name': validation.Optional(validation.TYPE_STR), + 'kind': validation.Optional(validation.TYPE_STR), + 'model': OPTIONAL_EVALUATED_CALLABLE, + 'connector': validation.TYPE_STR, + 'connector_options': validation.Optional(ConnectorOptions, {}), + 'use_model_on_export': validation.Optional(validation.TYPE_BOOL), + 'sort_key_from_entity': OPTIONAL_EVALUATED_CALLABLE, + 'post_import_function': OPTIONAL_EVALUATED_CALLABLE, + 'post_export_function': OPTIONAL_EVALUATED_CALLABLE, + 'property_map': validation.Repeated(PropertyEntry, default=[]), + } + + def CheckInitialized(self): + """Check that all required (combinations) of fields are set. + + Also fills in computed properties. + + Raises: + InvalidConfiguration: if the config is invalid. + """ + if not self.kind and not self.model: + raise bulkloader_errors.InvalidConfiguration( + 'Neither kind nor model specified for transformer.') + if self.kind and self.model: + raise bulkloader_errors.InvalidConfiguration( + 'Both kind and model specified for transformer.') + + if self.model: + self.kind = self.model.method.kind() + else: + if self.use_model_on_export: + raise bulkloader_errors.InvalidConfiguration( + 'No model class specified but use_model_on_export is true.') + if not self.name: + self.name = self.kind + + if not self.connector: + raise bulkloader_errors.InvalidConfiguration('No connector specified.') + + property_names = set() + for prop in self.property_map: + if prop.property in property_names: + raise bulkloader_errors.InvalidConfiguration( + 'Duplicate property specified for property %s in transform %s' % + (prop.property, self.name)) + property_names.add(prop.property) + + +class PythonPreambleEntry(validation.Validated): + """Python modules to import at initialization time, typically models.""" + + ATTRIBUTES = {'import': validation.TYPE_STR, + 'as': validation.Optional(validation.TYPE_STR), + } + + def CheckInitialized(self): + """Check that all required fields are set, and update global state. + + The imports specified in the preamble are imported at this time. + """ + python_import = getattr(self, 'import') + topname = python_import.split('.')[0] + module_name = getattr(self, 'as') + if not module_name: + module_name = python_import.split('.')[-1] + + __import__(python_import, _global_temp_globals) + _global_temp_globals[topname] = sys.modules[topname] + _global_temp_globals[module_name] = sys.modules[python_import] + + +class BulkloaderEntry(validation.Validated): + """Root of the bulkloader configuration.""" + + ATTRIBUTES = { + 'python_preamble': + validation.Optional(validation.Repeated(PythonPreambleEntry)), + 'transformers': validation.Repeated(TransformerEntry), + } + + +def load_config(stream, config_globals): + """Load a configuration file and generate importer and exporter classes. + + Args: + stream: Stream containing config YAML. + config_globals: Dict to use to reference globals for code in the config. + + Returns: + BulkloaderEntry + + Raises: + InvalidConfiguration: If the config is invalid. + """ + builder = yaml_object.ObjectBuilder(BulkloaderEntry) + handler = yaml_builder.BuilderHandler(builder) + listener = yaml_listener.EventListener(handler) + global _global_temp_globals + _global_temp_globals = config_globals + try: + listener.Parse(stream) + finally: + _global_temp_globals = None + + bulkloader_infos = handler.GetResults() + if len(bulkloader_infos) < 1: + raise bulkloader_errors.InvalidConfiguration('No configuration specified.') + if len(bulkloader_infos) > 1: + raise bulkloader_errors.InvalidConfiguration( + 'Multiple sections in configuration.') + bulkloader_info = bulkloader_infos[0] + if not bulkloader_info.transformers: + raise bulkloader_errors.InvalidConfiguration('No transformers specified.') + return bulkloader_info diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_parser.pyc b/google_appengine/google/appengine/ext/bulkload/bulkloader_parser.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbc4c3c2580037d237390b2495622b69709b855a GIT binary patch literal 11944 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.py b/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.py new file mode 100755 index 0000000..3db4ee8 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Wizard to generate bulkloader configuration. + +Helper functions to call from the bulkloader.yaml. +The wizard is run by having bulkloader.py download datastore statistics +(http://code.google.com/appengine/docs/python/datastore/stats.html , +specifically __Stat_PropertyType_PropertyName_Kind__) configured with +bulkloader_wizard.yaml. +""" + + +PROPERTY_DUPE_WARNING = ( + ' # Warning: This property is a duplicate, but with a different type.\n' + ' # TODO: Edit this transform so only one property with this name ' + 'remains.\n') + +KIND_PREAMBLE = """ +- kind: %(kind_name)s + connector: # TODO: Choose a connector here: csv, simplexml, etc... + connector_options: + # TODO: Add connector options here--these are specific to each connector. + property_map: + - property: __key__ + external_name: key + export_transform: transform.key_id_or_name_as_string + +""" + + +class StatPostTransform(object): + """Create text to insert between properties and filter out 'bad' properties. + + This class is a callable post_export_function which saves state + across multiple calls. + + It uses this saved state to determine if each entity is the first entity seen + of a new kind, a duplicate kind/propertyname entry, or just a new property + in the current kind being processed. + + It will suppress bad output by returning None for NULL property types and + __private__ types (notably the stats themselves). + """ + + def __init__(self): + """Constructor. + + Attributes: + seen_properties: (kind, propertyname) -> number of times seen before. If + seen more than once, this is a duplicate property for the kind. + last_seen: Previous kind seen. If it changes, this is a new kind. + """ + self.seen_properties = {} + self.last_seen = None + + def __call__(self, instance, dictionary, bulkload_state): + """Implementation of StatPropertyTypePropertyNameKindPostExport. + + See class docstring for more info. + + Args: + instance: Input, current entity being exported. + dictionary: Output, dictionary created by property_map transforms. + bulkload_state: Passed bulkload_state. + + Returns: + Dictionary--same object as passed in dictionary. + """ + kind_name = dictionary['kind_name'] + property_name = dictionary['property_name'] + property_type = dictionary['property_type'] + + if kind_name.startswith('__'): + return None + if property_type == 'NULL': + return None + + property_key = kind_name, property_name + if kind_name != self.last_seen: + self.last_seen = kind_name + separator = KIND_PREAMBLE % dictionary + elif property_key in self.seen_properties: + separator = PROPERTY_DUPE_WARNING % dictionary + else: + separator = '' + self.seen_properties[property_key] = ( + self.seen_properties.get(property_key, 0) + 1) + + dictionary['separator'] = separator + return dictionary + + +TYPE_TO_TRANSFORM_MAP = { + 'Blob': ('transform.blobproperty_from_base64', + 'base64.b64encode'), + 'Boolean': ('transform.regexp_bool(\'true\', re.IGNORECASE)', + None), + 'ByteString': ('transform.bytestring_from_base64', 'base64.b64encode'), + 'Category': ('db.Category', None), + 'Date/Time': ('transform.import_date_time(\'%Y-%m-%dT%H:%M:%S\')', + 'transform.export_date_time(\'%Y-%m-%dT%H:%M:%S\')'), + 'Email': ('db.Email', None), + 'Float': ('transform.none_if_empty(float)', None), + + 'Integer': ('transform.none_if_empty(int)', None), + 'Key': ('transform.create_foreign_key(\'TODO: fill in Kind name\')', + 'transform.key_id_or_name_as_string'), + 'Link': ('db.Link', None), + + 'PhoneNumber': ('db.PhoneNumber', None), + 'PostalAddress': ('db.PostalAddress', None), + 'Rating': ('transform.none_if_empty(db.Rating)', None), + 'String': (None, None), + 'Text': ('db.Text', None), + 'User': ('transform.none_if_empty(users.User) # Assumes email address', + None), +} + + +def DatastoreTypeToTransforms(property_type): + """Return the import/export_transform lines for a datastore type. + + Args: + property_type: Property type from the KindPropertyNamePropertyTypeStat. + + Returns: + Strings for use in a bulkloader.yaml as transforms. This + may be '' (no transform needed), or one or two lines with import_transform + or export_transform. + """ + import_transform, export_transform = TYPE_TO_TRANSFORM_MAP.get(property_type, + (None, None)) + transform = [] + if import_transform: + transform.append(' import_transform: %s\n' % import_transform) + if export_transform: + transform.append(' export_transform: %s\n' % export_transform) + + return ''.join(transform) diff --git a/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.yaml b/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.yaml new file mode 100755 index 0000000..7471d71 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/bulkloader_wizard.yaml @@ -0,0 +1,87 @@ +# Copyright 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# Transform description for generating bulkloader configuration from a +# bulk download of the datastore stats. +# + +python_preamble: +- import: google.appengine.ext.bulkload.transform +- import: google.appengine.ext.bulkload.bulkloader_wizard +- import: google.appengine.api.datastore + +transformers: +- kind: __Stat_PropertyType_PropertyName_Kind__ + connector: simpletext + connector_options: + mode: nonewline + prolog: | + # Autogenerated bulkloader.yaml file. + # You must edit this file before using it. TODO: Remove this line when done. + # At a minimum address the items marked with TODO: + # * Fill in connector and connector_options + # * Review the property_map. + # - Ensure the 'external_name' matches the name of your CSV column, + # XML tag, etc. + # - Check that __key__ property is what you want. Its value will become + # the key name on import, and on export the value will be the Key + # object. If you would like automatic key generation on import and + # omitting the key on export, you can remove the entire __key__ + # property from the property map. + + # If you have module(s) with your model classes, add them here. Also + # change the kind properties to model_class. + python_preamble: + - import: base64 + - import: re + - import: google.appengine.ext.bulkload.transform + - import: google.appengine.ext.bulkload.bulkloader_wizard + - import: google.appengine.ext.db + - import: google.appengine.api.datastore + - import: google.appengine.api.users + + transformers: + template: | + %(separator)s - property: %(property_name)s + external_name: %(property_name)s + # Type: %(property_type)s Stats: %(count)s properties of this type in this kind. + %(transforms)s + sort_key_from_entity: + "lambda entity: '_'.join((entity.get('kind_name', ''), + entity.get('property_name', ''), + entity.get('property_type', '')))" + property_map: + - property: __key__ + external_name: key + export_transform: datastore.Key.name + - property: kind_name + external_name: kind_name + default_value: MISSING_KIND_NAME + - property: property_name + external_name: property_name + - property: property_type + external_name: property_type + export: + - external_name: property_type + - external_name: transforms + export_transform: bulkloader_wizard.DatastoreTypeToTransforms + - property: bytes + external_name: bytes + - property: count + external_name: count + - property: timestamp + external_name: timestamp + export_transform: transform.export_date_time('%Y%m%dT%H:%M') + post_export_function: bulkloader_wizard.StatPostTransform() diff --git a/google_appengine/google/appengine/ext/bulkload/connector_interface.py b/google_appengine/google/appengine/ext/bulkload/connector_interface.py new file mode 100755 index 0000000..11603c9 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/connector_interface.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkloader interfaces for the format reader/writers.""" + + + + + +class ConnectorInterface(object): + """Abstract base class describing the external Connector interface. + + The External Connector interface describes the transition between an external + data source, e.g. CSV file, XML file, or some sort of database interface, and + the intermediate bulkloader format, which is a dictionary or similar + structure representing the external transformation of the data. + + On import, the generate_import_record generator is the only method called. + + On export, the initialize_export method is called once, followed by a call + to write_dict for each record, followed by a call to finalize_export. + + The bulkload_state is a BulkloadState object from + google.appengine.ext.bulkload.bulkload_config. The interesting properties + to a Connector object are the loader_opts and exporter_opts, which are strings + passed in from the bulkloader command line. + """ + + def generate_import_record(self, filename, bulkload_state): + """A function which returns an interator over dictionaries. + + This is the only method used on import. + + Args: + filename: The --filename argument passed in on the bulkloader command + line. This value is opaque to the bulkloader and thus could specify + any sort of descriptor for your generator. + bulkload_state: Passed in BulkloadConfig.BulkloadState object. + + Returns: + An iterator describing an individual record. Typically a dictionary, + to be used with dict_to_model. Typically implemented as a generator. + """ + raise NotImplementedError + + def initialize_export(self, filename, bulkload_state): + """Initialize the output file. + + Args: + filename: The string given as the --filename flag argument. + bulkload_state: Passed in BulkloadConfig.BulkloadState object. + + These values are opaque to the bulkloader and thus could specify + any sort of descriptor for your exporter. + """ + raise NotImplementedError + + def write_dict(self, dictionary): + """Write one record for the specified entity. + + Args: + dictionary: A post-transform dictionary. + """ + raise NotImplementedError + + def finalize_export(self): + """Performs finalization actions after every record is written.""" + raise NotImplementedError + + +def create_from_options(options, name='unknown'): + """Factory using an options dictionary. + + This is frequently implemented as the constructor on the connector class, + or a static or class method on the connector class. + + Args: + options: Parsed dictionary from yaml file, interpretation is up to the + implementor of this class. + name: Identifier of this transform to be used in error messages. + + Returns: + An object which implements the ConnectorInterface interface. + """ + raise NotImplementedError diff --git a/google_appengine/google/appengine/ext/bulkload/connector_interface.pyc b/google_appengine/google/appengine/ext/bulkload/connector_interface.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49f09bc97279fc2b724b0a108010ed9b12e69065 GIT binary patch literal 4021 zcwW6%-EJH;6rSCr+a@Y40jc7G3tyo{q`OkZRa6im6cMR}vMGPzV&t9i?3&KlgY8K= zTtc6SH{lU@1)hL&j_sLAvuZ1Z5-DDf$38#b`Ofi~zy5h^_wV=f$5|5ow&4FW__zBo zA#P!|-`iJ}WiPg<(=T zVG5oJ%L;9nTMPWHxMN1(hWUk&u*1&WJ8NvP2l|wiPHM%L!d(cV7+0MZke74EnbnQS#6A(KnwuYWUes+f>j$~g>qLQBHz4B^bgF!*Jv6c^^Ib(s=?rWT3-nz z3>cL9#ngyQn>;QfgdQ=i%9d5at#!^aUX~({(!}##np6NPFXe9{^~>=HNb#pY5Ta7( zveXywxNN~)e1#xsM&wJ;ePXBJfQUC~(-}NbNHtX0u^|GST8DTOtT7EvX-O}C5va%L z3eK#wUX^0T>sqLlRAL5z&Z3ZRl4e>J66o+W22@xIL2Yy`jFZB8NBDq+K@o?rAxJ*# zsji(xb_4*!N~8g9hG>Im6j(z}fa6k;wIpRgpiEa43M$cT*qv>Ks(~qbglV=5gG-X% zN0^+a(=o1n2;(6u8bwqKYK;JPDYVObM-~4_7yxZQGb6z$SYhCkSVFrPC=`iBt)(y~6 zH)YOjEizfOoh+`}KAWjz)QA9bv4v{T=fx}>xlFSI_M|gBlI_SdW)t@UGz$-UMCTBY zi_j11AcZ8C=Q3{~%Y9OS+ifi|71~QxV?T%j|ChqY<3hSM?MR(YE1ipSco;%e3Pc`u za!i$%96gyLm)yHBoM?C4?|x*AHd7><+eTK2vT(Ox5<}|lz{6#brg(eMV1I^TzJ?My zSDE2@{)N6!xY5piI;LGZhJ|^*0QotOg6COGwzR(&>ek%@`I{@;N2nh2Ai4Wy>GA=* zJ?_ez=X~Sp#u1xeOLCvfY$ea}uJJiDAbwHuRmcASlUtx0?jy^4AmoJ&=FpN_yBuemBFP@#`WlVfX>>C8$LrBwp7$d6*=G1@B_l zZXiORmIv&R)!MrI-5ou6c@?~I1ka&a{myGd5Eb`#@>d;nlWY`VdnI&;^%IZ=p4xa* z`?iFWGeF4#v@roUOdFwv@{KB;P_rQ#qlm^ex_>vpN_MHic+z1op58>4vD&36W^tMl zUMZH>ro_ja^qA``O&8ejF0dnC;EBb~B~Px!odqg+FA1@l>`lfydpoyxZjqUddZs|# zo2A5`@b*iN3R<{b!tDg@3x#w@>D|6dD2#x@qukZo2J;9k_RTH!8!9e^28zB3@J@p{ zwB=CcWX9rp>^e4|A`|*-3^rmdHCRgrjW%^wEw9SZ+!J!pWIIn-@Sv$f*$j!@xq&ug zn~hzrSBkHF@juS7FhGUq_w?C6xSye_3%pCg9c%e2G+CEg1859FD;Yi&qJ9ry-*I*y zYs0UktF?vV9uT418!f&*I#M^0!B;0#7X&{IrhJ#g47+nOMY9&S(O66!?>_N(@pf|U W2}HU6&}a9j_<3y6yvgL=C;tLJ%gr_b literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/constants.py b/google_appengine/google/appengine/ext/bulkload/constants.py new file mode 100755 index 0000000..af3c857 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/constants.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" Constants used by both the bulkload server-side mixin handler and the +command-line client. +""" + + +KIND_PARAM = 'kind' +CSV_PARAM = 'csv' diff --git a/google_appengine/google/appengine/ext/bulkload/constants.pyc b/google_appengine/google/appengine/ext/bulkload/constants.pyc new file mode 100644 index 0000000000000000000000000000000000000000..330d310fec9e3fe7b3b7089aae94d48f2c82ac8e GIT binary patch literal 345 zcwR-1!Ab)$5Qei|sk-1zJbB1*FKu2x#L`|$wWtLTix75`VK+3JESuTdKBkZ0EBFLX zu%&_d^3P1j{PX>Lll|Q5XT^>up?IPQcLdE?K}=YYFg;?$i0PDV@{8F2B=EU+0ZWI0 z4*@i<4!rW%aBP5AgWXzRY9659K|cvb18>dVINp>_Tj)7m@oA=f+mfDG`#*~B#yLBD~UJ|6#UP*}=>D6+*kgv1%*$ZA%I^TT!XL;P_jPOPfjc-9X)t6o@ z{FjUVf~dW(Ey%L#z|~~oSP`|Qd5$d$_v^(Gx{rb>)Jy>|WjbozgaXL=^0HYmT A^8f$< literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/csv_connector.py b/google_appengine/google/appengine/ext/bulkload/csv_connector.py new file mode 100755 index 0000000..8f39849 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/csv_connector.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkloader CSV reading and writing. + +Handle the CSV format specified in a bulkloader.yaml file. +""" + + + + + +import codecs +import cStringIO +import csv +import encodings +import encodings.ascii +import encodings.cp1252 +import encodings.latin_1 +import encodings.utf_8 + +from google.appengine.ext.bulkload import bulkloader_errors +from google.appengine.ext.bulkload import connector_interface + + +def utf8_recoder(stream, encoding): + """Generator that reads an encoded stream and reencodes to UTF-8.""" + for line in codecs.getreader(encoding)(stream): + yield line.encode('utf-8') + + +class UnicodeDictWriter(object): + """Based on UnicodeWriter in http://docs.python.org/library/csv.html.""" + + def __init__(self, stream, fieldnames, encoding='utf-8', **kwds): + """Initialzer. + + Args: + stream: Stream to write to. + fieldnames: Fieldnames to pass to the DictWriter. + encoding: Desired encoding. + kwds: Additional arguments to pass to the DictWriter. + """ + writer = codecs.getwriter(encoding) + if (writer is encodings.utf_8.StreamWriter or + writer is encodings.ascii.StreamWriter or + writer is encodings.latin_1.StreamWriter or + writer is encodings.cp1252.StreamWriter): + self.no_recoding = True + self.encoder = codecs.getencoder(encoding) + self.writer = csv.DictWriter(stream, fieldnames, **kwds) + else: + self.no_recoding = False + self.encoder = codecs.getencoder('utf-8') + self.queue = cStringIO.StringIO() + self.writer = csv.DictWriter(self.queue, fieldnames, **kwds) + self.stream = writer(stream) + + def writerow(self, row): + """Wrap writerow method.""" + row_encoded = dict([(k, self.encoder(v)[0]) for (k, v) in row.iteritems()]) + self.writer.writerow(row_encoded) + if self.no_recoding: + return + + data = self.queue.getvalue() + data = data.decode('utf-8') + self.stream.write(data) + self.queue.truncate(0) + + +class CsvConnector(connector_interface.ConnectorInterface): + """Read/write a (possibly encoded) CSV file.""" + + @classmethod + def create_from_options(cls, options, name): + """Factory using an options dictionary. + + Args: + options: Dictionary of options: + columns: 'from_header' or blank. + column_list: overrides columns specifically. + encoding: encoding of the file. e.g. 'utf-8' (default), 'windows-1252'. + skip_import_header_row: True to ignore the header line on import. + Defaults False, except must be True if columns=from_header. + print_export_header_row: True to print a header line on export. + Defaults to False except if columns=from_header. + import_options: Other kwargs to pass in, like "dialect". + export_options: Other kwargs to pass in, like "dialect". + name: The name of this transformer, for use in error messages. + + Returns: + CsvConnector object described by the specified options. + + Raises: + InvalidConfiguration: If the config is invalid. + """ + column_list = options.get('column_list', None) + columns = None + if not column_list: + columns = options.get('columns', 'from_header') + if columns != 'from_header': + raise bulkloader_errors.InvalidConfiguration( + 'CSV columns must be "from_header", or a column_list ' + 'must be specified. (In transformer name %s.)' % name) + csv_encoding = options.get('encoding', 'utf-8') + + + skip_import_header_row = options.get('skip_import_header_row', + columns == 'from_header') + if columns == 'from_header' and not skip_import_header_row: + raise bulkloader_errors.InvalidConfiguration( + 'When CSV columns are "from_header", the header row must always ' + 'be skipped. (In transformer name %s.)' % name) + print_export_header_row = options.get('print_export_header_row', + columns == 'from_header') + import_options = options.get('import_options', {}) + export_options = options.get('export_options', {}) + return cls(columns, column_list, skip_import_header_row, + print_export_header_row, csv_encoding, import_options, + export_options) + + def __init__(self, columns, column_list, skip_import_header_row, + print_export_header_row, csv_encoding=None, + import_options=None, export_options=None): + """Initializer. + + Args: + columns: 'from_header' or blank + column_list: overrides columns specifically. + skip_import_header_row: True to ignore the header line on import. + Defaults False, except must be True if columns=from_header. + print_export_header_row: True to print a header line on export. + Defaults to False except if columns=from_header. + csv_encoding: encoding of the file. + import_options: Other kwargs to pass in, like "dialect". + export_options: Other kwargs to pass in, like "dialect". + """ + + self.columns = columns + self.from_header = (columns == 'from_header') + self.column_list = column_list + self.skip_import_header_row = skip_import_header_row + self.print_export_header_row = print_export_header_row + self.csv_encoding = csv_encoding + self.dict_generator = None + self.output_stream = None + self.csv_writer = None + self.bulkload_state = None + self.import_options = import_options or {} + self.export_options = export_options or {} + + def generate_import_record(self, filename, bulkload_state): + """Generator, yields dicts for nodes found as described in the options. + + Args: + filename: Filename to read. + bulkload_state: Passed bulkload_state. + + Yields: + Neutral dict, one per row in the CSV file. + """ + self.bulkload_state = bulkload_state + input_stream = open(filename) + input_stream = utf8_recoder(input_stream, self.csv_encoding) + + self.dict_generator = csv.DictReader(input_stream, self.column_list, + **self.import_options) + discard_line = self.skip_import_header_row and not self.from_header + + line_number = 0 + for input_dict in self.dict_generator: + line_number = line_number + 1 + if discard_line: + discard_line = False + continue + + decoded_dict = {} + for key, value in input_dict.iteritems(): + if key == None: + raise bulkloader_errors.InvalidImportData( + 'Got more values in row than headers on line %d.' + % (line_number)) + if not self.column_list: + key = unicode(key, 'utf-8') + if value: + value = unicode(value, 'utf-8') + decoded_dict[key] = value + yield decoded_dict + + def initialize_export(self, filename, bulkload_state): + """Initialize the output file. + + Args: + filename: Filename to write. + bulkload_state: Passed bulkload_state. + """ + self.bulkload_state = bulkload_state + self.output_stream = open(filename, 'wb') + + def __initialize_csv_writer(self, dictionary): + """Actual initialization, happens on the first entity being written.""" + write_header = self.print_export_header_row + if self.from_header: + export_column_list = tuple(dictionary) + else: + export_column_list = self.column_list + + self.csv_writer = UnicodeDictWriter(self.output_stream, export_column_list, + self.csv_encoding, + **self.export_options) + if write_header: + self.csv_writer.writerow(dict(zip(export_column_list, + export_column_list))) + + def write_dict(self, dictionary): + """Write one record for the specified entity.""" + if not self.csv_writer: + self.__initialize_csv_writer(dictionary) + self.csv_writer.writerow(dictionary) + + def finalize_export(self): + self.output_stream.close() diff --git a/google_appengine/google/appengine/ext/bulkload/csv_connector.pyc b/google_appengine/google/appengine/ext/bulkload/csv_connector.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fab50f9cbba59dd792572708a0c96133eb44593d GIT binary patch literal 8675 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/simplexml_connector.py b/google_appengine/google/appengine/ext/bulkload/simplexml_connector.py new file mode 100755 index 0000000..053c4e3 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/simplexml_connector.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkloader XML reading and writing. + +Handle the XML format specified in a bulkloader.yaml file. +""" + + + + +import codecs +import logging +import re + +try: + from xml.etree import ElementTree +except ImportError: + import google + import cElementTree as ElementTree +from xml.sax import saxutils + +from google.appengine.ext.bulkload import bulkloader_errors +from google.appengine.ext.bulkload import connector_interface + + +NODE_PATH_ONLY_RE = '(/[a-zA-Z][a-zA-Z0-9]*)+$' + + +class SimpleXmlConnector(connector_interface.ConnectorInterface): + """Read/write a simply-structured XML file and convert dicts for each record. + + A simply-structed XML file is one where we can locate all interesting nodes + with a simple (ElementTree supported) xpath, and each node contains either + all the info we care about as child (and not grandchild) nodes with text or + as attributes. + We'll also pass the entire node in case the developer wants to do something + more interesting with it (occasional grandchildren, parents, etc.). + + This is of course a fairly expensive way to read XML--we build a DOM, then + copy parts of it into a dict. A pull model would work well with the interface + too. + """ + + ELEMENT_CENTRIC = 1 + ATTRIBUTE_CENTRIC = 2 + + @classmethod + def create_from_options(cls, options, name): + """Factory using an options dictionary. + + Args: + options: Dictionary of options. Must contain: + * xpath_to_nodes: The xpath to select a record. + * style: 'element_centric' or 'attribute_centric' + name: The name of this transformer, for use in error messages. + + Returns: + XmlConnector connector object described by the specified options. + + Raises: + InvalidConfiguration: If the config is invalid. + """ + xpath_to_nodes = options.get('xpath_to_nodes') + if not xpath_to_nodes: + raise bulkloader_errors.InvalidConfiguration( + 'simplexml must specify xpath_to_nodes. (In transformer named %s)' % + name) + + if not re.match(NODE_PATH_ONLY_RE, xpath_to_nodes): + logging.warning('simplexml export only supports very simple ' + '/root/to/node xpath_to_nodes for now.') + + xml_style = options.get('style') + xml_style_mapping = { + 'element_centric': cls.ELEMENT_CENTRIC, + 'attribute_centric': cls.ATTRIBUTE_CENTRIC, + } + if xml_style not in xml_style_mapping: + raise bulkloader_errors.InvalidConfiguration( + 'simplexml must specify one of these valid xml_style options: "%s". ' + 'You specified %s in transformer named %s.' % + ('", "'.join(xml_style_mapping.keys()), xml_style, + name)) + return cls(xpath_to_nodes, xml_style_mapping[xml_style]) + + def __init__(self, xpath_to_nodes, xml_style): + """Constructor. + + Args: + xpath_to_nodes: xpath to the nodes to run over. + xml_style: ELEMENT_CENTRIC or ATTRIBUTE_CENTRIC--we'll + either convert the list of elements to a dict (last element of the same + name will be used) or the list of attributes. + + Raises: + InvalidConfiguration: If the config is invalid. + """ + self.xpath_to_nodes = xpath_to_nodes + assert xml_style in (self.ELEMENT_CENTRIC, self.ATTRIBUTE_CENTRIC) + self.xml_style = xml_style + self.output_stream = None + self.bulkload_state = None + self.depth = 0 + + if re.match(NODE_PATH_ONLY_RE, xpath_to_nodes): + self.node_list = self.xpath_to_nodes.split('/')[1:] + self.entity_node = self.node_list[-1] + self.node_list = self.node_list[:-1] + else: + self.node_list = None + self.entity_node = None + self.node_list = None + + def generate_import_record(self, filename, bulkload_state): + """Generator, yields dicts for nodes found as described in the options.""" + self.bulkload_state = bulkload_state + tree = ElementTree.parse(filename) + xpath_to_nodes = self.xpath_to_nodes + if (len(xpath_to_nodes) > 1 and xpath_to_nodes[0] == '/' + and xpath_to_nodes[1] != '/'): + if not tree.getroot().tag == xpath_to_nodes.split('/')[1]: + return + xpath_to_nodes = '/' + xpath_to_nodes.split('/', 2)[2] + nodes = tree.findall(xpath_to_nodes) + + for node in nodes: + if self.xml_style == self.ELEMENT_CENTRIC: + input_dict = {} + for child in node.getchildren(): + if not child.tag in input_dict: + input_dict[child.tag] = child.text + else: + input_dict = dict(node.items()) + input_dict['__node__'] = node + yield input_dict + + def initialize_export(self, filename, bulkload_state): + """Initialize the output file.""" + self.bulkload_state = bulkload_state + if not self.node_list: + raise bulkloader_errors.InvalidConfiguration( + 'simplexml export only supports simple /root/to/node xpath_to_nodes ' + 'for now.') + self.output_stream = codecs.open(filename, 'wb', 'utf-8') + self.output_stream.write('\n') + self.depth = 0 + for node in self.node_list: + self.output_stream.write('%s<%s>\n' % (' ' * self.depth, node)) + self.depth += 1 + self.indent = ' ' * self.depth + + def write_iterable_as_elements(self, values): + """Write a dict as elements, possibly recursively.""" + if isinstance(values, dict): + values = values.iteritems() + for (name, value) in values: + if isinstance(value, basestring): + self.output_stream.write('%s <%s>%s\n' % (self.indent, name, + saxutils.escape(value), + name)) + else: + self.output_stream.write('%s <%s>\n' % (self.indent, name)) + self.depth += 1 + self.indent = ' ' * self.depth + self.write_iterable_as_elements(value) + self.depth -= 1 + self.indent = ' ' * self.depth + self.output_stream.write('%s \n' % (self.indent, name)) + + def write_dict(self, dictionary): + """Write one record for the specified entity.""" + if self.xml_style == self.ELEMENT_CENTRIC: + self.output_stream.write('%s<%s>\n' % (self.indent, self.entity_node)) + self.write_iterable_as_elements(dictionary) + self.output_stream.write('%s\n' % (self.indent, self.entity_node)) + else: + self.output_stream.write('%s<%s ' % (self.indent, self.entity_node)) + for (name, value) in dictionary.iteritems(): + self.output_stream.write('%s=%s ' % (name, saxutils.quoteattr(value))) + self.output_stream.write('/>\n') + + def finalize_export(self): + if not self.output_stream: + return + for node in reversed(self.node_list): + self.depth -= 1 + self.output_stream.write('%s\n' % (' ' * self.depth, node)) + self.output_stream.close() + self.output_stream = None + diff --git a/google_appengine/google/appengine/ext/bulkload/simplexml_connector.pyc b/google_appengine/google/appengine/ext/bulkload/simplexml_connector.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19a1c8e140fec31bb733249bece676de9b7b2ad6 GIT binary patch literal 7373 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/bulkload/transform.py b/google_appengine/google/appengine/ext/bulkload/transform.py new file mode 100755 index 0000000..e055fc4 --- /dev/null +++ b/google_appengine/google/appengine/ext/bulkload/transform.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Bulkloader Transform Helper functions. + +A collection of helper functions for bulkloading data, typically referenced +from a bulkloader.yaml file. +""" + + + + +import base64 + +import datetime +import os +import re +import tempfile + +from google.appengine.api import datastore +from google.appengine.api import datastore_types +from google.appengine.ext.bulkload import bulkloader_errors + + +CURRENT_PROPERTY = None + +KEY_TYPE_NAME = 'name' +KEY_TYPE_ID = 'ID' + + +# Decorators + + +def none_if_empty(fn): + """A wrapper for a value to return None if it's empty. Useful on import. + + Can be used in config files (e.g. "transform.none_if_empty(int)" or + as a decorator. + + Args: + fn: Single argument transform function. + + Returns: + Wrapped function. + """ + + def wrapper(value): + if value == '' or value is None: + return None + return fn(value) + + return wrapper + + +def empty_if_none(fn): + """A wrapper for a value to return '' if it's None. Useful on export. + + Can be used in config files (e.g. "transform.empty_if_none(unicode)" or + as a decorator. + + Args: + fn: Single argument transform function. + + Returns: + Wrapped function. + """ + + def wrapper(value): + if value is None: + return '' + return fn(value) + + return wrapper + + +# Key helpers. + + +def create_foreign_key(kind, key_is_id=False): + """A method to make one-level Key objects. + + These are typically used in ReferenceProperty in Python, where the reference + value is a key with kind (or model) name name. + + This helper method does not support keys with parents. Use create_deep_key + instead to create keys with parents. + + Args: + kind: The kind name of the reference as a string. + key_is_id: If true, convert the key into an integer to be used as an id. + If false, leave the key in the input format (typically a string). + + Returns: + Single argument method which parses a value into a Key of kind entity_kind. + """ + + def generate_foreign_key_lambda(value): + if key_is_id: + value = int(value) + return datastore.Key.from_path(kind, value) + + return generate_foreign_key_lambda + + +def create_deep_key(*path_info): + """A method to make multi-level Key objects. + + Generates multi-level key from multiple fields in the input dictionary. + + This is typically used for Keys for entities which have variable parent keys, + e.g. ones with owned relationships. It can used for both __key__ and + references. + + Use create_foreign_key as a simpler way to create single level keys. + + Args: + path_info: List of tuples, describing (kind, property, is_id=False). + kind: The kind name. + property: The external property in the current import dictionary, or + transform.CURRENT_PROPERTY for the value passed to the transform. + is_id: Converts value to int and treats as numeric ID if True, otherwise + the value is a string name. Default is False. + Example: + create_deep_key(('rootkind', 'rootcolumn'), + ('childkind', 'childcolumn', True), + ('leafkind', transform.CURRENT_PROPERTY)) + + Returns: + Transform method which parses the info from the current neutral dictionary + into a Key with parents as described by path_info. + """ + validated_path_info = [] + for level_info in path_info: + if len(level_info) == 3: + key_is_id = level_info[2] + elif len(level_info) == 2: + key_is_id = False + else: + raise bulkloader_errors.InvalidConfiguration( + 'Each list in create_deep_key must specify exactly 2 or 3 ' + 'parameters, (kind, property, is_id=False). You specified: %s' % + repr(path_info)) + kind_name = level_info[0] + property_name = level_info[1] + validated_path_info.append((kind_name, property_name, key_is_id)) + + def create_deep_key_lambda(value, bulkload_state): + path = [] + for kind_name, property_name, key_is_id in validated_path_info: + if property_name is CURRENT_PROPERTY: + name_or_id = value + else: + name_or_id = bulkload_state.current_dictionary[property_name] + + if key_is_id: + name_or_id = int(name_or_id) + + path += [kind_name, name_or_id] + + return datastore.Key.from_path(*path) + + return create_deep_key_lambda + + +def _key_id_or_name_n(key, index): + """Internal helper function for key id and name transforms. + + Args: + key: A datastore key. + index: The depth in the key to return; 0 is root, -1 is leaf. + + Returns: + The id or name of the nth deep sub key in key. + """ + if not key: + return None + path = key.to_path() + if not path: + return None + path_index = (index * 2) + 1 + return path[path_index] + + +def key_id_or_name_as_string_n(index): + """Pull out the nth (0-based) key id or name from a key which has parents. + + If a key is present, return its id or name as a string. + + Note that this loses the distinction between integer IDs and strings + which happen to look like integers. Use key_type to distinguish them. + + This is a useful complement to create_deep_key. + + Args: + index: The depth of the id or name to extract. Zero is the root key. + Negative one is the leaf key. + + Returns: + Function extracting the name or ID of the key at depth index, as a unicode + string. Returns '' if key is empty (unsaved), otherwise raises IndexError + if the key is not as deep as described. + """ + + def transform_function(key): + id_or_name = _key_id_or_name_n(key, index) + if not id_or_name: + return u'' + return unicode(id_or_name) + + return transform_function + +# # Commonly used helper which returns the value of the leaf key. +key_id_or_name_as_string = key_id_or_name_as_string_n(-1) + + +def key_type_n(index): + """Pull out the nth (0-based) key type from a key which has parents. + + This is most useful when paired with key_id_or_name_as_string_n. + This is a useful complement to create_deep_key. + + Args: + index: The depth of the id or name to extract. Zero is the root key. + Negative one is the leaf key. + + Returns: + Method returning the type ('ID' or 'name') of the key at depth index. + Returns '' if key is empty (unsaved), otherwise raises IndexError + if the key is not as deep as described. + """ + + def transform_function(key): + id_or_name = _key_id_or_name_n(key, index) + if id_or_name is None: + return '' + if isinstance(id_or_name, basestring): + return KEY_TYPE_NAME + return KEY_TYPE_ID + + return transform_function + +# # Commonly used helper which returns the type of the leaf key. +key_type = key_type_n(-1) + + +def key_kind_n(index): + """Pull out the nth (0-based) key kind from a key which has parents. + + This is a useful complement to create_deep_key. + + Args: + index: The depth of the id or name to extract. Zero is the root key. + Negative one is the leaf key. + + Returns: + Function returning the kind of the key at depth index, or raising + IndexError if the key is not as deep as described. + """ + + @empty_if_none + def transform_function(key): + path = key.to_path() + path_index = (index * 2) + return unicode(path[path_index]) + + return transform_function + +# Commonly used helper which returns the kind of the leaf key. +key_kind = key_kind_n(-1) + + +# Blob and ByteString helpers. + + +@none_if_empty +def blobproperty_from_base64(value): + """Return a datastore blob property containing the base64 decoded value.""" + decoded_value = base64.b64decode(value) + return datastore_types.Blob(decoded_value) + + +@none_if_empty +def bytestring_from_base64(value): + """Return a datastore bytestring property from a base64 encoded value.""" + decoded_value = base64.b64decode(value) + return datastore_types.ByteString(decoded_value) + + +def blob_to_file(filename_hint_propertyname=None, + directory_hint=''): + """Write the blob contents to a file, and replace them with the filename. + + Args: + filename_hint_propertyname: If present, the filename will begin with + the contents of this value in the entity being exported. + directory_hint: If present, the files will be stored in this directory. + + Returns: + A function which writes the input blob to a file. + """ + directory = [] + + def transform_function(value, bulkload_state): + if not directory: + parent_dir = os.path.dirname(bulkload_state.filename) + directory.append(os.path.join(parent_dir, directory_hint)) + if directory[0] and not os.path.exists(directory[0]): + os.makedirs(directory[0]) + + filename_hint = 'blob_' + suffix = '' + filename = '' + if filename_hint_propertyname: + filename_hint = bulkload_state.current_entity[filename_hint_propertyname] + filename = os.path.join(directory[0], filename_hint) + if os.path.exists(filename): + filename = '' + (filename_hint, suffix) = os.path.splitext(filename_hint) + if not filename: + filename = tempfile.mktemp(suffix, filename_hint, directory[0]) + f = open(filename, 'wb') + f.write(value) + f.close() + return filename + + return transform_function + + +# Formatted string helpers: Extract, convert to boolean, date, or list. + + +def import_date_time(format, _strptime=None): + """A wrapper around strptime. Also returns None if the input is empty. + + Args: + format: Format string for strptime. + + Returns: + Single argument method which parses a string into a datetime using format. + """ + if not _strptime: + _strptime = datetime.datetime.strptime + + def import_date_time_lambda(value): + if not value: + return None + return _strptime(value, format) + + return import_date_time_lambda + + +def export_date_time(format): + """A wrapper around strftime. Also returns '' if the input is None. + + Args: + format: Format string for strftime. + + Returns: + Single argument method which convers a datetime into a string using format. + """ + + def export_date_time_lambda(value): + if not value: + return '' + return datetime.datetime.strftime(value, format) + + return export_date_time_lambda + + +def regexp_extract(pattern): + """Return first group in the value matching the pattern. + + Args: + pattern: A regular expression to match on with at least one group. + + Returns: + A single argument method which returns the first group matched, + or None if no match or no group was found. + """ + + def regexp_extract_lambda(value): + if not value: + return None + matches = re.match(pattern, value) + if not matches: + return None + return matches.group(1) + + return regexp_extract_lambda + + +def regexp_bool(regexp, flags=0): + """Return a boolean if the expression matches with re.match. + + Note that re.match anchors at the start but not end of the string. + + Args: + regexp: String, regular expression. + flags: Optional flags to pass to re.match. + + Returns: + Method which returns a Boolean if the expression matches. + """ + + def transform_function(value): + return bool(re.match(regexp, value, flags)) + + return transform_function + + +def split_string(delimeter): + """Split a string using the delimeter into a list. + + This is just a wrapper for string.split. + + Args: + delimeter: The delimiter to split the string on. + + Returns: + Method which splits the string into a list along the delimeter. + """ + + def split_string_lambda(value): + return value.split(delimeter) + + return split_string_lambda + + +def join_list(delimeter): + """Join a list into a string using the delimeter. + + This is just a wrapper for string.join. + + Args: + delimeter: The delimiter to use when joining the string. + + Returns: + Method which joins the list into a string with the delimeter. + """ + + def join_string_lambda(value): + return delimeter.join(value) + + return join_string_lambda + + +def list_from_multiproperty(*external_names): + """Create a list from multiple properties. + + Args: + external_names: List of the properties to use. + + Returns: + Transform function which returns a list of the properties in external_names. + """ + + def list_from_multiproperty_lambda(unused_value, bulkload_state): + result = [] + for external_name in external_names: + value = bulkload_state.current_dictionary.get(external_name) + if value: + result.append(value) + return result + + return list_from_multiproperty_lambda + + +def property_from_list(index): + """Return the Nth item from a list, or '' if the list is shorter. + + Args: + index: Item in the list to return. + + Returns: + Function returning the item from a list, or '' if the list is too short. + """ + + @empty_if_none + def property_from_list_lambda(values): + if len(values) > index: + return values[index] + return '' + + return property_from_list_lambda + + +# SimpleXML list Helpers + + +def list_from_child_node(xpath, suppress_blank=False): + """Return a list property from child nodes of the current xml node. + + This applies only the simplexml helper, as it assumes __node__, the current + ElementTree node corresponding to the import record. + + Sample usage for structure: + + + A1 + A2 + + + + property: activities + external_name: VisitActivities # Ignored on import, used on export. + import_transform: list_from_xml_node('VisitActivities/Activity') + export_transform: child_node_from_list('Activity') + + Args: + xpath: XPath to run on the current node. + suppress_blank: if True, ndoes with no text will be skipped. + + Returns: + Transform function which works as described in the args. + """ + + def list_from_child_node_lambda(unused_value, bulkload_state): + result = [] + for node in bulkload_state.current_dictionary['__node__'].findall(xpath): + if node.text: + result.append(node.text) + elif not suppress_blank: + result.append('') + return result + + return list_from_child_node_lambda + + +def child_node_from_list(child_node_name): + """Return a value suitable for generating an XML child node on export. + + The return value is a list of tuples which the simplexml connector will + use to build a child node. + + See also list_from_child_node + + Args: + child_node_name: The name to use for each child node. + + Returns: + Transform function which works as described in the args. + """ + + def child_node_from_list_lambda(values): + return [(child_node_name, value) for value in values] + + return child_node_from_list_lambda diff --git a/google_appengine/google/appengine/ext/db/__init__.py b/google_appengine/google/appengine/ext/db/__init__.py new file mode 100755 index 0000000..cca84c0 --- /dev/null +++ b/google_appengine/google/appengine/ext/db/__init__.py @@ -0,0 +1,3432 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Simple, schema-based database abstraction layer for the datastore. + +Modeled after Django's abstraction layer on top of SQL databases, +http://www.djangoproject.com/documentation/mode_api/. Ours is a little simpler +and a lot less code because the datastore is so much simpler than SQL +databases. + +The programming model is to declare Python subclasses of the Model class, +declaring datastore properties as class members of that class. So if you want to +publish a story with title, body, and created date, you would do it like this: + + class Story(db.Model): + title = db.StringProperty() + body = db.TextProperty() + created = db.DateTimeProperty(auto_now_add=True) + +You can create a new Story in the datastore with this usage pattern: + + story = Story(title='My title') + story.body = 'My body' + story.put() + +You query for Story entities using built in query interfaces that map directly +to the syntax and semantics of the datastore: + + stories = Story.all().filter('date >=', yesterday).order('-date') + for story in stories: + print story.title + +The Property declarations enforce types by performing validation on assignment. +For example, the DateTimeProperty enforces that you assign valid datetime +objects, and if you supply the "required" option for a property, you will not +be able to assign None to that property. + +We also support references between models, so if a story has comments, you +would represent it like this: + + class Comment(db.Model): + story = db.ReferenceProperty(Story) + body = db.TextProperty() + +When you get a story out of the datastore, the story reference is resolved +automatically the first time it is referenced, which makes it easy to use +model instances without performing additional queries by hand: + + comment = Comment.get(key) + print comment.story.title + +Likewise, you can access the set of comments that refer to each story through +this property through a reverse reference called comment_set, which is a Query +preconfigured to return all matching comments: + + story = Story.get(key) + for comment in story.comment_set: + print comment.body + +""" + + + + + + +import base64 +import copy +import datetime +import logging +import re +import time +import urlparse +import warnings + +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api import namespace_manager +from google.appengine.api import users +from google.appengine.datastore import datastore_pb + +Error = datastore_errors.Error +BadValueError = datastore_errors.BadValueError +BadPropertyError = datastore_errors.BadPropertyError +BadRequestError = datastore_errors.BadRequestError +EntityNotFoundError = datastore_errors.EntityNotFoundError +BadArgumentError = datastore_errors.BadArgumentError +QueryNotFoundError = datastore_errors.QueryNotFoundError +TransactionNotFoundError = datastore_errors.TransactionNotFoundError +Rollback = datastore_errors.Rollback +TransactionFailedError = datastore_errors.TransactionFailedError +BadFilterError = datastore_errors.BadFilterError +BadQueryError = datastore_errors.BadQueryError +BadKeyError = datastore_errors.BadKeyError +InternalError = datastore_errors.InternalError +NeedIndexError = datastore_errors.NeedIndexError +Timeout = datastore_errors.Timeout +CommittedButStillApplying = datastore_errors.CommittedButStillApplying + +ValidationError = BadValueError + +Key = datastore_types.Key +Category = datastore_types.Category +Link = datastore_types.Link +Email = datastore_types.Email +GeoPt = datastore_types.GeoPt +IM = datastore_types.IM +PhoneNumber = datastore_types.PhoneNumber +PostalAddress = datastore_types.PostalAddress +Rating = datastore_types.Rating +Text = datastore_types.Text +Blob = datastore_types.Blob +ByteString = datastore_types.ByteString +BlobKey = datastore_types.BlobKey + +READ_CAPABILITY = datastore.READ_CAPABILITY +WRITE_CAPABILITY = datastore.WRITE_CAPABILITY + +STRONG_CONSISTENCY = datastore.STRONG_CONSISTENCY +EVENTUAL_CONSISTENCY = datastore.EVENTUAL_CONSISTENCY + +KEY_RANGE_EMPTY = "Empty" +"""Indicates the given key range is empty and the datastore's +automatic ID allocator will not assign keys in this range to new +entities. +""" + +KEY_RANGE_CONTENTION = "Contention" +"""Indicates the given key range is empty but the datastore's +automatic ID allocator may assign new entities keys in this range. +However it is safe to manually assign keys in this range +if either of the following is true: + + - No other request will insert entities with the same kind and parent + as the given key range until all entities with manually assigned + keys from this range have been written. + - Overwriting entities written by other requests with the same kind + and parent as the given key range is acceptable. + +The datastore's automatic ID allocator will not assign a key to a new +entity that will overwrite an existing entity, so once the range is +populated there will no longer be any contention. +""" + +KEY_RANGE_COLLISION = "Collision" +"""Indicates that entities with keys inside the given key range +already exist and writing to this range will overwrite those entities. +Additionally the implications of KEY_RANGE_COLLISION apply. If +overwriting entities that exist in this range is acceptable it is safe +to use the given range. + +The datastore's automatic ID allocator will never assign a key to +a new entity that will overwrite an existing entity so entities +written by the user to this range will never be overwritten by +an entity with an automatically assigned key. +""" + + +_kind_map = {} + + +_SELF_REFERENCE = object() + + +_RESERVED_WORDS = set(['key_name']) + + + + +class NotSavedError(Error): + """Raised when a saved-object action is performed on a non-saved object.""" + + +class KindError(BadValueError): + """Raised when an entity is used with incorrect Model.""" + + +class PropertyError(Error): + """Raised when non-existent property is referenced.""" + + +class DuplicatePropertyError(Error): + """Raised when a property is duplicated in a model definition.""" + + +class ConfigurationError(Error): + """Raised when a property or model is improperly configured.""" + + +class ReservedWordError(Error): + """Raised when a property is defined for a reserved word.""" + + +class DerivedPropertyError(Error): + """Raised when attempting to assign a value to a derived property.""" + + +_ALLOWED_PROPERTY_TYPES = set([ + basestring, + str, + unicode, + bool, + int, + long, + float, + Key, + datetime.datetime, + datetime.date, + datetime.time, + Blob, + ByteString, + Text, + users.User, + Category, + Link, + Email, + GeoPt, + IM, + PhoneNumber, + PostalAddress, + Rating, + BlobKey, + ]) + +_ALLOWED_EXPANDO_PROPERTY_TYPES = set(_ALLOWED_PROPERTY_TYPES) +_ALLOWED_EXPANDO_PROPERTY_TYPES.update((list, tuple, type(None))) + +_OPERATORS = ['<', '<=', '>', '>=', '=', '==', '!=', 'in'] +_FILTER_REGEX = re.compile( + '^\s*([^\s]+)(\s+(%s)\s*)?$' % '|'.join(_OPERATORS), + re.IGNORECASE | re.UNICODE) + + +def class_for_kind(kind): + """Return base-class responsible for implementing kind. + + Necessary to recover the class responsible for implementing provided + kind. + + Args: + kind: Entity kind string. + + Returns: + Class implementation for kind. + + Raises: + KindError when there is no implementation for kind. + """ + try: + return _kind_map[kind] + except KeyError: + raise KindError('No implementation for kind \'%s\'' % kind) + + +def check_reserved_word(attr_name): + """Raise an exception if attribute name is a reserved word. + + Args: + attr_name: Name to check to see if it is a reserved word. + + Raises: + ReservedWordError when attr_name is determined to be a reserved word. + """ + if datastore_types.RESERVED_PROPERTY_NAME.match(attr_name): + raise ReservedWordError( + "Cannot define property. All names both beginning and " + "ending with '__' are reserved.") + + if attr_name in _RESERVED_WORDS or attr_name in dir(Model): + raise ReservedWordError( + "Cannot define property using reserved word '%(attr_name)s'. " + "If you would like to use this name in the datastore consider " + "using a different name like %(attr_name)s_ and adding " + "name='%(attr_name)s' to the parameter list of the property " + "definition." % locals()) + + +def query_descendants(model_instance): + """Returns a query for all the descendants of a model instance. + + Args: + model_instance: Model instance to find the descendants of. + + Returns: + Query that will retrieve all entities that have the given model instance + as an ancestor. Unlike normal ancestor queries, this does not include the + ancestor itself. + """ + + result = Query().ancestor(model_instance); + result.filter(datastore_types._KEY_SPECIAL_PROPERTY + ' >', + model_instance.key()); + return result; + + +def model_to_protobuf(model_instance, _entity_class=datastore.Entity): + """Encodes a model instance as a protocol buffer. + + Args: + model_instance: Model instance to encode. + Returns: + entity_pb.EntityProto representation of the model instance + """ + return model_instance._populate_entity(_entity_class).ToPb() + + +def model_from_protobuf(pb, _entity_class=datastore.Entity): + """Decodes a model instance from a protocol buffer. + + Args: + pb: The protocol buffer representation of the model instance. Can be an + entity_pb.EntityProto or str encoding of an entity_bp.EntityProto + + Returns: + Model instance resulting from decoding the protocol buffer + """ + entity = _entity_class.FromPb(pb) + return class_for_kind(entity.kind()).from_entity(entity) + + +def _initialize_properties(model_class, name, bases, dct): + """Initialize Property attributes for Model-class. + + Args: + model_class: Model class to initialize properties for. + """ + model_class._properties = {} + property_source = {} + + def get_attr_source(name, cls): + for src_cls in cls.mro(): + if name in src_cls.__dict__: + return src_cls + + defined = set() + for base in bases: + if hasattr(base, '_properties'): + property_keys = set(base._properties.keys()) + duplicate_property_keys = defined & property_keys + for dupe_prop_name in duplicate_property_keys: + old_source = property_source[dupe_prop_name] = get_attr_source( + dupe_prop_name, property_source[dupe_prop_name]) + new_source = get_attr_source(dupe_prop_name, base) + if old_source != new_source: + raise DuplicatePropertyError( + 'Duplicate property, %s, is inherited from both %s and %s.' % + (dupe_prop_name, old_source.__name__, new_source.__name__)) + property_keys -= duplicate_property_keys + if property_keys: + defined |= property_keys + property_source.update(dict.fromkeys(property_keys, base)) + model_class._properties.update(base._properties) + + for attr_name in dct.keys(): + attr = dct[attr_name] + if isinstance(attr, Property): + check_reserved_word(attr_name) + if attr_name in defined: + raise DuplicatePropertyError('Duplicate property: %s' % attr_name) + defined.add(attr_name) + model_class._properties[attr_name] = attr + attr.__property_config__(model_class, attr_name) + + model_class._unindexed_properties = frozenset( + prop.name for name, prop in model_class._properties.items() + if not prop.indexed) + + +def _coerce_to_key(value): + """Returns the value's key. + + Args: + value: a Model or Key instance or string encoded key or None + + Returns: + The corresponding key, or None if value is None. + """ + if value is None: + return None + + value, multiple = datastore.NormalizeAndTypeCheck( + value, (Model, Key, basestring)) + + if len(value) > 1: + raise datastore_errors.BadArgumentError('Expected only one model or key') + value = value[0] + + if isinstance(value, Model): + return value.key() + elif isinstance(value, basestring): + return Key(value) + else: + return value + + +class PropertiedClass(type): + """Meta-class for initializing Model classes properties. + + Used for initializing Properties defined in the context of a model. + By using a meta-class much of the configuration of a Property + descriptor becomes implicit. By using this meta-class, descriptors + that are of class Model are notified about which class they + belong to and what attribute they are associated with and can + do appropriate initialization via __property_config__. + + Duplicate properties are not permitted. + """ + + def __init__(cls, name, bases, dct, map_kind=True): + """Initializes a class that might have property definitions. + + This method is called when a class is created with the PropertiedClass + meta-class. + + Loads all properties for this model and its base classes in to a dictionary + for easy reflection via the 'properties' method. + + Configures each property defined in the new class. + + Duplicate properties, either defined in the new class or defined separately + in two base classes are not permitted. + + Properties may not assigned to names which are in the list of + _RESERVED_WORDS. It is still possible to store a property using a reserved + word in the datastore by using the 'name' keyword argument to the Property + constructor. + + Args: + cls: Class being initialized. + name: Name of new class. + bases: Base classes of new class. + dct: Dictionary of new definitions for class. + + Raises: + DuplicatePropertyError when a property is duplicated either in the new + class or separately in two base classes. + ReservedWordError when a property is given a name that is in the list of + reserved words, attributes of Model and names of the form '__.*__'. + """ + super(PropertiedClass, cls).__init__(name, bases, dct) + + _initialize_properties(cls, name, bases, dct) + + if map_kind: + _kind_map[cls.kind()] = cls + + +class Property(object): + """A Property is an attribute of a Model. + + It defines the type of the attribute, which determines how it is stored + in the datastore and how the property values are validated. Different property + types support different options, which change validation rules, default + values, etc. The simplest example of a property is a StringProperty: + + class Story(db.Model): + title = db.StringProperty() + """ + + creation_counter = 0 + + def __init__(self, + verbose_name=None, + name=None, + default=None, + required=False, + validator=None, + choices=None, + indexed=True): + """Initializes this Property with the given options. + + Args: + verbose_name: User friendly name of property. + name: Storage name for property. By default, uses attribute name + as it is assigned in the Model sub-class. + default: Default value for property if none is assigned. + required: Whether property is required. + validator: User provided method used for validation. + choices: User provided set of valid property values. + indexed: Whether property is indexed. + """ + self.verbose_name = verbose_name + self.name = name + self.default = default + self.required = required + self.validator = validator + self.choices = choices + self.indexed = indexed + self.creation_counter = Property.creation_counter + Property.creation_counter += 1 + + def __property_config__(self, model_class, property_name): + """Configure property, connecting it to its model. + + Configure the property so that it knows its property name and what class + it belongs to. + + Args: + model_class: Model class which Property will belong to. + property_name: Name of property within Model instance to store property + values in. By default this will be the property name preceded by + an underscore, but may change for different subclasses. + """ + self.model_class = model_class + if self.name is None: + self.name = property_name + + def __get__(self, model_instance, model_class): + """Returns the value for this property on the given model instance. + + See http://docs.python.org/ref/descriptors.html for a description of + the arguments to this class and what they mean.""" + if model_instance is None: + return self + + try: + return getattr(model_instance, self._attr_name()) + except AttributeError: + return None + + def __set__(self, model_instance, value): + """Sets the value for this property on the given model instance. + + See http://docs.python.org/ref/descriptors.html for a description of + the arguments to this class and what they mean. + """ + value = self.validate(value) + setattr(model_instance, self._attr_name(), value) + + def default_value(self): + """Default value for unassigned values. + + Returns: + Default value as provided by __init__(default). + """ + return self.default + + def validate(self, value): + """Assert that provided value is compatible with this property. + + Args: + value: Value to validate against this Property. + + Returns: + A valid value, either the input unchanged or adapted to the + required type. + + Raises: + BadValueError if the value is not appropriate for this + property in any way. + """ + if self.empty(value): + if self.required: + raise BadValueError('Property %s is required' % self.name) + else: + if self.choices: + match = False + for choice in self.choices: + if choice == value: + match = True + if not match: + raise BadValueError('Property %s is %r; must be one of %r' % + (self.name, value, self.choices)) + if self.validator is not None: + self.validator(value) + return value + + def empty(self, value): + """Determine if value is empty in the context of this property. + + For most kinds, this is equivalent to "not value", but for kinds like + bool, the test is more subtle, so subclasses can override this method + if necessary. + + Args: + value: Value to validate against this Property. + + Returns: + True if this value is considered empty in the context of this Property + type, otherwise False. + """ + return not value + + def get_value_for_datastore(self, model_instance): + """Datastore representation of this property. + + Looks for this property in the given model instance, and returns the proper + datastore representation of the value that can be stored in a datastore + entity. Most critically, it will fetch the datastore key value for + reference properties. + + Args: + model_instance: Instance to fetch datastore value from. + + Returns: + Datastore representation of the model value in a form that is + appropriate for storing in the datastore. + """ + return self.__get__(model_instance, model_instance.__class__) + + def make_value_from_datastore(self, value): + """Native representation of this property. + + Given a value retrieved from a datastore entity, return a value, + possibly converted, to be stored on the model instance. Usually + this returns the value unchanged, but a property class may + override this when it uses a different datatype on the model + instance than on the entity. + + This API is not quite symmetric with get_value_for_datastore(), + because the model instance on which to store the converted value + may not exist yet -- we may be collecting values to be passed to a + model constructor. + + Args: + value: value retrieved from the datastore entity. + + Returns: + The value converted for use as a model instance attribute. + """ + return value + + def _require_parameter(self, kwds, parameter, value): + """Sets kwds[parameter] to value. + + If kwds[parameter] exists and is not value, raises ConfigurationError. + + Args: + kwds: The parameter dict, which maps parameter names (strings) to values. + parameter: The name of the parameter to set. + value: The value to set it to. + """ + if parameter in kwds and kwds[parameter] != value: + raise ConfigurationError('%s must be %s.' % (parameter, value)) + + kwds[parameter] = value + + def _attr_name(self): + """Attribute name we use for this property in model instances. + + DO NOT USE THIS METHOD. + """ + return '_' + self.name + + data_type = str + + def datastore_type(self): + """Deprecated backwards-compatible accessor method for self.data_type.""" + return self.data_type + + +class Model(object): + """Model is the superclass of all object entities in the datastore. + + The programming model is to declare Python subclasses of the Model class, + declaring datastore properties as class members of that class. So if you want + to publish a story with title, body, and created date, you would do it like + this: + + class Story(db.Model): + title = db.StringProperty() + body = db.TextProperty() + created = db.DateTimeProperty(auto_now_add=True) + + A model instance can have a single parent. Model instances without any + parent are root entities. It is possible to efficiently query for + instances by their shared parent. All descendents of a single root + instance also behave as a transaction group. This means that when you + work one member of the group within a transaction all descendents of that + root join the transaction. All operations within a transaction on this + group are ACID. + """ + + __metaclass__ = PropertiedClass + + def __init__(self, + parent=None, + key_name=None, + _app=None, + _from_entity=False, + **kwds): + """Creates a new instance of this model. + + To create a new entity, you instantiate a model and then call put(), + which saves the entity to the datastore: + + person = Person() + person.name = 'Bret' + person.put() + + You can initialize properties in the model in the constructor with keyword + arguments: + + person = Person(name='Bret') + + We initialize all other properties to the default value (as defined by the + properties in the model definition) if they are not provided in the + constructor. + + Args: + parent: Parent instance for this instance or None, indicating a top- + level instance. + key_name: Name for new model instance. + _from_entity: Intentionally undocumented. + kwds: Keyword arguments mapping to properties of model. Also: + key: Key instance for this instance, if provided makes parent and + key_name redundant (they do not need to be set but if they are + they must match the key). + """ + namespace = None + if isinstance(_app, tuple): + if len(_app) != 2: + raise BadArgumentError('_app must have 2 values if type is tuple.') + _app, namespace = _app + key = kwds.get('key', None) + if key is not None: + if isinstance(key, (tuple, list)): + key = Key.from_path(*key) + if isinstance(key, basestring): + key = Key(encoded=key) + if not isinstance(key, Key): + raise TypeError('Expected Key type; received %s (is %s)' % + (key, key.__class__.__name__)) + if not key.has_id_or_name(): + raise BadKeyError('Key must have an id or name') + if key.kind() != self.kind(): + raise BadKeyError('Expected Key kind to be %s; received %s' % + (self.kind(), key.kind())) + if _app is not None and key.app() != _app: + raise BadKeyError('Expected Key app to be %s; received %s' % + (_app, key.app())) + if namespace is not None and key.namespace() != namespace: + raise BadKeyError('Expected Key namespace to be %s; received %s' % + (namespace, key.namespace())) + if key_name and key_name != key.name(): + raise BadArgumentError('Cannot use key and key_name at the same time' + ' with different values') + if parent and parent != key.parent(): + raise BadArgumentError('Cannot use key and parent at the same time' + ' with different values') + namespace = key.namespace() + self._key = key + self._key_name = None + self._parent = None + self._parent_key = None + else: + if key_name == '': + raise BadKeyError('Name cannot be empty.') + elif key_name is not None and not isinstance(key_name, basestring): + raise BadKeyError('Name must be string type, not %s' % + key_name.__class__.__name__) + + if parent is not None: + if not isinstance(parent, (Model, Key)): + raise TypeError('Expected Model type; received %s (is %s)' % + (parent, parent.__class__.__name__)) + if isinstance(parent, Model) and not parent.has_key(): + raise BadValueError( + "%s instance must have a complete key before it can be used as a " + "parent." % parent.kind()) + if isinstance(parent, Key): + self._parent_key = parent + self._parent = None + else: + self._parent_key = parent.key() + self._parent = parent + else: + self._parent_key = None + self._parent = None + self._key_name = key_name + self._key = None + + if self._parent_key is not None: + if namespace is not None and self._parent_key.namespace() != namespace: + raise BadArgumentError( + 'Expected parent namespace to be %r; received %r' % + (namespace, self._parent_key.namespace())) + namespace = self._parent_key.namespace() + + self._entity = None + if _app is not None and isinstance(_app, Key): + raise BadArgumentError('_app should be a string; received Key(\'%s\'):\n' + ' This may be the result of passing \'key\' as ' + 'a positional parameter in SDK 1.2.6. Please ' + 'only pass \'key\' as a keyword parameter.' % _app) + if namespace is None: + namespace = namespace_manager.get_namespace() + + self._app = _app + self.__namespace = namespace + + for prop in self.properties().values(): + if prop.name in kwds: + value = kwds[prop.name] + else: + value = prop.default_value() + try: + prop.__set__(self, value) + except DerivedPropertyError, e: + if prop.name in kwds and not _from_entity: + raise + + def key(self): + """Unique key for this entity. + + This property is only available if this entity is already stored in the + datastore or if it has a full key, so it is available if this entity was + fetched returned from a query, or after put() is called the first time + for new entities, or if a complete key was given when constructed. + + Returns: + Datastore key of persisted entity. + + Raises: + NotSavedError when entity is not persistent. + """ + if self.is_saved(): + return self._entity.key() + elif self._key: + return self._key + elif self._key_name: + parent = self._parent_key or (self._parent and self._parent.key()) + self._key = Key.from_path(self.kind(), self._key_name, parent=parent, + _app=self._app, namespace=self.__namespace) + return self._key + else: + raise NotSavedError() + + def _to_entity(self, entity): + """Copies information from this model to provided entity. + + Args: + entity: Entity to save information on. + """ + for prop in self.properties().values(): + datastore_value = prop.get_value_for_datastore(self) + if datastore_value == []: + try: + del entity[prop.name] + except KeyError: + pass + else: + entity[prop.name] = datastore_value + + entity.set_unindexed_properties(self._unindexed_properties) + + def _populate_internal_entity(self, _entity_class=datastore.Entity): + """Populates self._entity, saving its state to the datastore. + + After this method is called, calling is_saved() will return True. + + Returns: + Populated self._entity + """ + self._entity = self._populate_entity(_entity_class=_entity_class) + for attr in ('_key_name', '_key'): + try: + delattr(self, attr) + except AttributeError: + pass + return self._entity + + def put(self, **kwargs): + """Writes this model instance to the datastore. + + If this instance is new, we add an entity to the datastore. + Otherwise, we update this instance, and the key will remain the + same. + + Returns: + The key of the instance (either the existing key or a new key). + + Raises: + TransactionFailedError if the data could not be committed. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + self._populate_internal_entity() + return datastore.Put(self._entity, rpc=rpc) + + save = put + + def _populate_entity(self, _entity_class=datastore.Entity): + """Internal helper -- Populate self._entity or create a new one + if that one does not exist. Does not change any state of the instance + other than the internal state of the entity. + + This method is separate from _populate_internal_entity so that it is + possible to call to_xml without changing the state of an unsaved entity + to saved. + + Returns: + self._entity or a new Entity which is not stored on the instance. + """ + if self.is_saved(): + entity = self._entity + else: + kwds = {'_app': self._app, 'namespace': self.__namespace, + 'unindexed_properties': self._unindexed_properties} + if self._key is not None: + if self._key.id(): + kwds['id'] = self._key.id() + else: + kwds['name'] = self._key.name() + if self._key.parent(): + kwds['parent'] = self._key.parent() + else: + if self._key_name is not None: + kwds['name'] = self._key_name + if self._parent_key is not None: + kwds['parent'] = self._parent_key + elif self._parent is not None: + kwds['parent'] = self._parent._entity + entity = _entity_class(self.kind(), **kwds) + + self._to_entity(entity) + return entity + + def delete(self, **kwargs): + """Deletes this entity from the datastore. + + Raises: + TransactionFailedError if the data could not be committed. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + datastore.Delete(self.key(), rpc=rpc) + self._key = self.key() + self._key_name = None + self._parent_key = None + self._entity = None + + + def is_saved(self): + """Determine if entity is persisted in the datastore. + + New instances of Model do not start out saved in the data. Objects which + are saved to or loaded from the Datastore will have a True saved state. + + Returns: + True if object has been persisted to the datastore, otherwise False. + """ + return self._entity is not None + + def has_key(self): + """Determine if this model instance has a complete key. + + When not using a fully self-assigned Key, ids are not assigned until the + data is saved to the Datastore, but instances with a key name always have + a full key. + + Returns: + True if the object has been persisted to the datastore or has a key + or has a key_name, otherwise False. + """ + return self.is_saved() or self._key or self._key_name + + def dynamic_properties(self): + """Returns a list of all dynamic properties defined for instance.""" + return [] + + def instance_properties(self): + """Alias for dyanmic_properties.""" + return self.dynamic_properties() + + def parent(self): + """Get the parent of the model instance. + + Returns: + Parent of contained entity or parent provided in constructor, None if + instance has no parent. + """ + if self._parent is None: + parent_key = self.parent_key() + if parent_key is not None: + self._parent = get(parent_key) + return self._parent + + def parent_key(self): + """Get the parent's key. + + This method is useful for avoiding a potential fetch from the datastore + but still get information about the instances parent. + + Returns: + Parent key of entity, None if there is no parent. + """ + if self._parent_key is not None: + return self._parent_key + elif self._parent is not None: + return self._parent.key() + elif self._entity is not None: + return self._entity.parent() + elif self._key is not None: + return self._key.parent() + else: + return None + + def to_xml(self, _entity_class=datastore.Entity): + """Generate an XML representation of this model instance. + + atom and gd:namespace properties are converted to XML according to their + respective schemas. For more information, see: + + http://www.atomenabled.org/developers/syndication/ + http://code.google.com/apis/gdata/common-elements.html + """ + entity = self._populate_entity(_entity_class) + return entity.ToXml() + + @classmethod + def get(cls, keys, **kwargs): + """Fetch instance from the datastore of a specific Model type using key. + + We support Key objects and string keys (we convert them to Key objects + automatically). + + Useful for ensuring that specific instance types are retrieved from the + datastore. It also helps that the source code clearly indicates what + kind of object is being retreived. Example: + + story = Story.get(story_key) + + Args: + keys: Key within datastore entity collection to find; or string key; + or list of Keys or string keys. + + Returns: + If a single key was given: a Model instance associated with key + for provided class if it exists in the datastore, otherwise + None; if a list of keys was given: a list whose items are either + a Model instance or None. + + Raises: + KindError if any of the retreived objects are not instances of the + type associated with call to 'get'. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + results = get(keys, rpc=rpc) + if results is None: + return None + + if isinstance(results, Model): + instances = [results] + else: + instances = results + + for instance in instances: + if not(instance is None or isinstance(instance, cls)): + raise KindError('Kind %r is not a subclass of kind %r' % + (instance.kind(), cls.kind())) + + return results + + @classmethod + def get_by_key_name(cls, key_names, parent=None, **kwargs): + """Get instance of Model class by its key's name. + + Args: + key_names: A single key-name or a list of key-names. + parent: Parent of instances to get. Can be a model or key. + """ + try: + parent = _coerce_to_key(parent) + except BadKeyError, e: + raise BadArgumentError(str(e)) + + rpc = datastore.GetRpcFromKwargs(kwargs) + key_names, multiple = datastore.NormalizeAndTypeCheck(key_names, basestring) + keys = [datastore.Key.from_path(cls.kind(), name, parent=parent) + for name in key_names] + if multiple: + return get(keys, rpc=rpc) + else: + return get(keys[0], rpc=rpc) + + @classmethod + def get_by_id(cls, ids, parent=None, **kwargs): + """Get instance of Model class by id. + + Args: + key_names: A single id or a list of ids. + parent: Parent of instances to get. Can be a model or key. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + if isinstance(parent, Model): + parent = parent.key() + ids, multiple = datastore.NormalizeAndTypeCheck(ids, (int, long)) + keys = [datastore.Key.from_path(cls.kind(), id, parent=parent) + for id in ids] + if multiple: + return get(keys, rpc=rpc) + else: + return get(keys[0], rpc=rpc) + + @classmethod + def get_or_insert(cls, key_name, **kwds): + """Transactionally retrieve or create an instance of Model class. + + This acts much like the Python dictionary setdefault() method, where we + first try to retrieve a Model instance with the given key name and parent. + If it's not present, then we create a new instance (using the *kwds + supplied) and insert that with the supplied key name. + + Subsequent calls to this method with the same key_name and parent will + always yield the same entity (though not the same actual object instance), + regardless of the *kwds supplied. If the specified entity has somehow + been deleted separately, then the next call will create a new entity and + return it. + + If the 'parent' keyword argument is supplied, it must be a Model instance. + It will be used as the parent of the new instance of this Model class if + one is created. + + This method is especially useful for having just one unique entity for + a specific identifier. Insertion/retrieval is done transactionally, which + guarantees uniqueness. + + Example usage: + + class WikiTopic(db.Model): + creation_date = db.DatetimeProperty(auto_now_add=True) + body = db.TextProperty(required=True) + + # The first time through we'll create the new topic. + wiki_word = 'CommonIdioms' + topic = WikiTopic.get_or_insert(wiki_word, + body='This topic is totally new!') + assert topic.key().name() == 'CommonIdioms' + assert topic.body == 'This topic is totally new!' + + # The second time through will just retrieve the entity. + overwrite_topic = WikiTopic.get_or_insert(wiki_word, + body='A totally different message!') + assert topic.key().name() == 'CommonIdioms' + assert topic.body == 'This topic is totally new!' + + Args: + key_name: Key name to retrieve or create. + **kwds: Keyword arguments to pass to the constructor of the model class + if an instance for the specified key name does not already exist. If + an instance with the supplied key_name and parent already exists, the + rest of these arguments will be discarded. + + Returns: + Existing instance of Model class with the specified key_name and parent + or a new one that has just been created. + + Raises: + TransactionFailedError if the specified Model instance could not be + retrieved or created transactionally (due to high contention, etc). + """ + def txn(): + entity = cls.get_by_key_name(key_name, parent=kwds.get('parent')) + if entity is None: + entity = cls(key_name=key_name, **kwds) + entity.put() + return entity + return run_in_transaction(txn) + + @classmethod + def all(cls, **kwds): + """Returns a query over all instances of this model from the datastore. + + Returns: + Query that will retrieve all instances from entity collection. + """ + return Query(cls, **kwds) + + @classmethod + def gql(cls, query_string, *args, **kwds): + """Returns a query using GQL query string. + + See appengine/ext/gql for more information about GQL. + + Args: + query_string: properly formatted GQL query string with the + 'SELECT * FROM ' part omitted + *args: rest of the positional arguments used to bind numeric references + in the query. + **kwds: dictionary-based arguments (for named parameters). + """ + return GqlQuery('SELECT * FROM %s %s' % (cls.kind(), query_string), + *args, **kwds) + + @classmethod + def _load_entity_values(cls, entity): + """Load dynamic properties from entity. + + Loads attributes which are not defined as part of the entity in + to the model instance. + + Args: + entity: Entity which contain values to search dyanmic properties for. + """ + entity_values = {} + for prop in cls.properties().values(): + if prop.name in entity: + try: + value = prop.make_value_from_datastore(entity[prop.name]) + entity_values[prop.name] = value + except KeyError: + entity_values[prop.name] = [] + + return entity_values + + @classmethod + def from_entity(cls, entity): + """Converts the entity representation of this model to an instance. + + Converts datastore.Entity instance to an instance of cls. + + Args: + entity: Entity loaded directly from datastore. + + Raises: + KindError when cls is incorrect model for entity. + """ + if cls.kind() != entity.kind(): + raise KindError('Class %s cannot handle kind \'%s\'' % + (repr(cls), entity.kind())) + + entity_values = cls._load_entity_values(entity) + if entity.key().has_id_or_name(): + entity_values['key'] = entity.key() + instance = cls(None, _from_entity=True, **entity_values) + if entity.is_saved(): + instance._entity = entity + del instance._key_name + del instance._key + return instance + + @classmethod + def kind(cls): + """Returns the datastore kind we use for this model. + + We just use the name of the model for now, ignoring potential collisions. + """ + return cls.__name__ + + @classmethod + def entity_type(cls): + """Soon to be removed alias for kind.""" + return cls.kind() + + @classmethod + def properties(cls): + """Returns a dictionary of all the properties defined for this model.""" + return dict(cls._properties) + + @classmethod + def fields(cls): + """Soon to be removed alias for properties.""" + return cls.properties() + + +def create_rpc(deadline=None, callback=None, read_policy=STRONG_CONSISTENCY): + """Create an rpc for use in configuring datastore calls. + + Args: + deadline: float, deadline for calls in seconds. + callback: callable, a callback triggered when this rpc completes, + accepts one argument: the returned rpc. + read_policy: flag, set to EVENTUAL_CONSISTENCY to enable eventually + consistent reads + + Returns: + A datastore.DatastoreRPC instance. + """ + return datastore.CreateRPC( + deadline=deadline, callback=callback, read_policy=read_policy) + +def get(keys, **kwargs): + """Fetch the specific Model instance with the given key from the datastore. + + We support Key objects and string keys (we convert them to Key objects + automatically). + + Args: + keys: Key within datastore entity collection to find; or string key; + or list of Keys or string keys. + + Returns: + If a single key was given: a Model instance associated with key + for if it exists in the datastore, otherwise None; if a list of + keys was given: a list whose items are either a Model instance or + None. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + keys, multiple = datastore.NormalizeAndTypeCheckKeys(keys) + try: + entities = datastore.Get(keys, rpc=rpc) + except datastore_errors.EntityNotFoundError: + assert not multiple + return None + models = [] + for entity in entities: + if entity is None: + model = None + else: + cls1 = class_for_kind(entity.kind()) + model = cls1.from_entity(entity) + models.append(model) + if multiple: + return models + assert len(models) == 1 + return models[0] + + +def put(models, **kwargs): + """Store one or more Model instances. + + Args: + models: Model instance or list of Model instances. + + Returns: + A Key or a list of Keys (corresponding to the argument's plurality). + + Raises: + TransactionFailedError if the data could not be committed. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + models, multiple = datastore.NormalizeAndTypeCheck(models, Model) + entities = [model._populate_internal_entity() for model in models] + keys = datastore.Put(entities, rpc=rpc) + if multiple: + return keys + assert len(keys) == 1 + return keys[0] + +save = put + + +def delete(models, **kwargs): + """Delete one or more Model instances. + + Args: + models: Model instance, key, key string or iterable thereof. + + Raises: + TransactionFailedError if the data could not be committed. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + + if isinstance(models, (basestring, Model, Key)): + models = [models] + else: + try: + models = iter(models) + except TypeError: + models = [models] + keys = [_coerce_to_key(v) for v in models] + + datastore.Delete(keys, rpc=rpc) + + +def allocate_ids(model, size, **kwargs): + """Allocates a range of IDs of size for the model_key defined by model. + + Allocates a range of IDs in the datastore such that those IDs will not + be automatically assigned to new entities. You can only allocate IDs + for model keys from your app. If there is an error, raises a subclass of + datastore_errors.Error. + + Args: + model: Model instance, Key or string to serve as a template specifying the + ID sequence in which to allocate IDs. Returned ids should only be used + in entities with the same parent (if any) and kind as this key. + + Returns: + (start, end) of the allocated range, inclusive. + """ + return datastore.AllocateIds(_coerce_to_key(model), size=size, **kwargs) + + +def allocate_id_range(model, start, end, **kwargs): + """Allocates a range of IDs with specific endpoints. + + Once these IDs have been allocated they may be provided manually to + newly created entities. + + Since the datastore's automatic ID allocator will never assign + a key to a new entity that will cause an existing entity to be + overwritten, entities written to the given key range will never be + overwritten. However, writing entities with manually assigned keys in this + range may overwrite existing entities (or new entities written by a + separate request) depending on the key range state returned. + + This method should only be used if you have an existing numeric id + range that you want to reserve, e.g. bulk loading entities that already + have IDs. If you don't care about which IDs you receive, use allocate_ids + instead. + + Args: + model: Model instance, Key or string to serve as a template specifying the + ID sequence in which to allocate IDs. Allocated ids should only be used + in entities with the same parent (if any) and kind as this key. + start: first id of the range to allocate, inclusive + end: last id of the range to allocate, inclusive + rpc: datastore.RPC to use for this request. + + Returns: + One of (KEY_RANGE_EMPTY, KEY_RANGE_CONTENTION, KEY_RANGE_COLLISION). If not + KEY_RANGE_EMPTY, this represents a potential issue with using the allocated + key range. + """ + key = _coerce_to_key(model) + datastore.NormalizeAndTypeCheck((start, end), (int, long)) + if start < 1 or end < 1: + raise BadArgumentError('Start %d and end %d must both be > 0.' % + (start, end)) + if start > end: + raise BadArgumentError('Range end %d cannot be less than start %d.' % + (end, start)) + + safe_start, safe_end = datastore.AllocateIds(key, max=end, **kwargs) + + race_condition = safe_start > start + + start_key = Key.from_path(key.kind(), start, parent=key.parent()) + end_key = Key.from_path(key.kind(), end, parent=key.parent()) + collision = (Query(keys_only=True).filter('__key__ >=', start_key) + .filter('__key__ <=', end_key).fetch(1)) + + if collision: + return KEY_RANGE_COLLISION + elif race_condition: + return KEY_RANGE_CONTENTION + else: + return KEY_RANGE_EMPTY + + +class Expando(Model): + """Dynamically expandable model. + + An Expando does not require (but can still benefit from) the definition + of any properties before it can be used to store information in the + datastore. Properties can be added to an expando object by simply + performing an assignment. The assignment of properties is done on + an instance by instance basis, so it is possible for one object of an + expando type to have different properties from another or even the same + properties with different types. It is still possible to define + properties on an expando, allowing those properties to behave the same + as on any other model. + + Example: + import datetime + + class Song(db.Expando): + title = db.StringProperty() + + crazy = Song(title='Crazy like a diamond', + author='Lucy Sky', + publish_date='yesterday', + rating=5.0) + + hoboken = Song(title='The man from Hoboken', + author=['Anthony', 'Lou'], + publish_date=datetime.datetime(1977, 5, 3)) + + crazy.last_minute_note=db.Text('Get a train to the station.') + + Possible Uses: + + One use of an expando is to create an object without any specific + structure and later, when your application mature and it in the right + state, change it to a normal model object and define explicit properties. + + Additional exceptions for expando: + + Protected attributes (ones whose names begin with '_') cannot be used + as dynamic properties. These are names that are reserved for protected + transient (non-persisted) attributes. + + Order of lookup: + + When trying to set or access an attribute value, any other defined + properties, such as methods and other values in __dict__ take precedence + over values in the datastore. + + 1 - Because it is not possible for the datastore to know what kind of + property to store on an undefined expando value, setting a property to + None is the same as deleting it from the expando. + + 2 - Persistent variables on Expando must not begin with '_'. These + variables considered to be 'protected' in Python, and are used + internally. + + 3 - Expando's dynamic properties are not able to store empty lists. + Attempting to assign an empty list to a dynamic property will raise + ValueError. Static properties on Expando can still support empty + lists but like normal Model properties is restricted from using + None. + """ + + _dynamic_properties = None + + def __init__(self, parent=None, key_name=None, _app=None, **kwds): + """Creates a new instance of this expando model. + + Args: + parent: Parent instance for this instance or None, indicating a top- + level instance. + key_name: Name for new model instance. + _app: Intentionally undocumented. + args: Keyword arguments mapping to properties of model. + """ + super(Expando, self).__init__(parent, key_name, _app, **kwds) + self._dynamic_properties = {} + storage_names = set(prop.name for prop in self.properties().values()) + for prop, value in kwds.iteritems(): + if prop not in storage_names and prop != 'key': + if not (hasattr(getattr(type(self), prop, None), '__set__')): + setattr(self, prop, value) + else: + check_reserved_word(prop) + + def __setattr__(self, key, value): + """Dynamically set field values that are not defined. + + Tries to set the value on the object normally, but failing that + sets the value on the contained entity. + + Args: + key: Name of attribute. + value: Value to set for attribute. Must be compatible with + datastore. + + Raises: + ValueError on attempt to assign empty list. + """ + check_reserved_word(key) + if (key[:1] != '_' and + + + + not hasattr(getattr(type(self), key, None), '__set__')): + if value == []: + raise ValueError('Cannot store empty list to dynamic property %s' % + key) + if type(value) not in _ALLOWED_EXPANDO_PROPERTY_TYPES: + raise TypeError("Expando cannot accept values of type '%s'." % + type(value).__name__) + if self._dynamic_properties is None: + self._dynamic_properties = {} + self._dynamic_properties[key] = value + else: + super(Expando, self).__setattr__(key, value) + + def __getattribute__(self, key): + """Get attribute from expando. + + Must be overridden to allow dynamic properties to obscure class attributes. + Since all attributes are stored in self._dynamic_properties, the normal + __getattribute__ does not attempt to access it until __setattr__ is called. + By then, the static attribute being overwritten has already been located + and returned from the call. + + This method short circuits the usual __getattribute__ call when finding a + dynamic property and returns it to the user via __getattr__. __getattr__ + is called to preserve backward compatibility with older Expando models + that may have overridden the original __getattr__. + + NOTE: Access to properties defined by Python descriptors are not obscured + because setting those attributes are done through the descriptor and does + not place those attributes in self._dynamic_properties. + """ + if not key.startswith('_'): + dynamic_properties = self._dynamic_properties + if dynamic_properties is not None and key in dynamic_properties: + return self.__getattr__(key) + + return super(Expando, self).__getattribute__(key) + + def __getattr__(self, key): + """If no explicit attribute defined, retrieve value from entity. + + Tries to get the value on the object normally, but failing that + retrieves value from contained entity. + + Args: + key: Name of attribute. + + Raises: + AttributeError when there is no attribute for key on object or + contained entity. + """ + _dynamic_properties = self._dynamic_properties + if _dynamic_properties is not None and key in _dynamic_properties: + return _dynamic_properties[key] + else: + return getattr(super(Expando, self), key) + + def __delattr__(self, key): + """Remove attribute from expando. + + Expando is not like normal entities in that undefined fields + can be removed. + + Args: + key: Dynamic property to be deleted. + """ + if self._dynamic_properties and key in self._dynamic_properties: + del self._dynamic_properties[key] + else: + object.__delattr__(self, key) + + def dynamic_properties(self): + """Determine which properties are particular to instance of entity. + + Returns: + Set of names which correspond only to the dynamic properties. + """ + if self._dynamic_properties is None: + return [] + return self._dynamic_properties.keys() + + def _to_entity(self, entity): + """Store to entity, deleting dynamic properties that no longer exist. + + When the expando is saved, it is possible that a given property no longer + exists. In this case, the property will be removed from the saved instance. + + Args: + entity: Entity which will receive dynamic properties. + """ + super(Expando, self)._to_entity(entity) + + if self._dynamic_properties is None: + self._dynamic_properties = {} + + for key, value in self._dynamic_properties.iteritems(): + entity[key] = value + + all_properties = set(self._dynamic_properties.iterkeys()) + all_properties.update(prop.name for prop in self.properties().itervalues()) + for key in entity.keys(): + if key not in all_properties: + del entity[key] + + @classmethod + def _load_entity_values(cls, entity): + """Load dynamic properties from entity. + + Expando needs to do a second pass to add the entity values which were + ignored by Model because they didn't have an corresponding predefined + property on the model. + + Args: + entity: Entity which contain values to search dyanmic properties for. + """ + entity_values = super(Expando, cls)._load_entity_values(entity) + for key, value in entity.iteritems(): + if key not in entity_values: + entity_values[str(key)] = value + return entity_values + + +def websafe_encode_cursor(compiled_cursor): + """Get a serialized cursor given a compiled cursor object. + + Args: + compiled_cursor: The datastore_pb.CompiledCursor cursor to serialize. + + Returns: + A base64-encoded serialized cursor. + """ + return base64.urlsafe_b64encode(compiled_cursor.Encode()) + + +def websafe_decode_cursor(cursor): + """Gets a datastore_pb.CompiledCursor given its serialized form. + + Args: + cursor: A serialized cursor as returned by websafe_encode_cursor. + + Returns: + A datastore_pb.CompiledCursor. + + Raises: + BadValueError: if the cursor argument is not a string type of does not + represent a serialized cursor. + """ + if not isinstance(cursor, basestring): + raise BadValueError( + 'Cursor must be a str or unicode instance, not a %s' + % type(cursor).__name__) + else: + cursor = str(cursor) + try: + decoded = base64.urlsafe_b64decode(cursor) + cursor = datastore_pb.CompiledCursor(decoded) + except (ValueError, TypeError), e: + raise datastore_errors.BadValueError( + 'Invalid cursor %s. Details: %s' % (cursor, e)) + except Exception, e: + if e.__class__.__name__ == 'ProtocolBufferDecodeError': + raise datastore_errors.BadValueError('Invalid cursor %s. ' + 'Details: %s' % (cursor, e)) + else: + raise + + return cursor + + +class _BaseQuery(object): + """Base class for both Query and GqlQuery.""" + _compile = False + + def __init__(self, model_class=None, keys_only=False, compile=True, + cursor=None, namespace=None): + """Constructor. + + Args: + model_class: Model class from which entities are constructed. + keys_only: Whether the query should return full entities or only keys. + compile: Whether the query should also return a compiled query. + cursor: A compiled query from which to resume. + namespace: The namespace to query. + """ + self._model_class = model_class + self._keys_only = keys_only + self._compile = compile + self._namespace = namespace + self.with_cursor(cursor) + + def is_keys_only(self): + """Returns whether this query is keys only. + + Returns: + True if this query returns keys, False if it returns entities. + """ + return self._keys_only + + def _get_query(self): + """Subclass must override (and not call their super method). + + Returns: + A datastore.Query instance representing the query. + """ + raise NotImplementedError + + def run(self, **kwargs): + """Iterator for this query. + + If you know the number of results you need, consider fetch() instead, + or use a GQL query with a LIMIT clause. It's more efficient. + + Args: + rpc: datastore.DatastoreRPC to use for this request. + + Returns: + Iterator for this query. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + raw_query = self._get_query() + iterator = raw_query.Run(rpc=rpc) + + if self._compile: + self._last_raw_query = raw_query + + if self._keys_only: + return iterator + else: + return _QueryIterator(self._model_class, iter(iterator)) + + def __iter__(self): + """Iterator for this query. + + If you know the number of results you need, consider fetch() instead, + or use a GQL query with a LIMIT clause. It's more efficient. + """ + return self.run() + + def get(self, **kwargs): + """Get first result from this. + + Beware: get() ignores the LIMIT clause on GQL queries. + + Returns: + First result from running the query if there are any, else None. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + results = self.fetch(1, rpc=rpc) + try: + return results[0] + except IndexError: + return None + + def count(self, limit=1000, **kwargs): + """Number of entities this query fetches. + + Beware: count() ignores the LIMIT clause on GQL queries. + + Args: + limit, a number. If there are more results than this, stop short and + just return this number. Providing this argument makes the count + operation more efficient. + + Returns: + Number of entities this query fetches. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + raw_query = self._get_query() + result = raw_query.Count(limit=limit, rpc=rpc) + if self._compile: + self._last_raw_query = raw_query + return result + + def fetch(self, limit, offset=0, **kwargs): + """Return a list of items selected using SQL-like limit and offset. + + Whenever possible, use fetch() instead of iterating over the query + results with run() or __iter__() . fetch() is more efficient. + + Beware: fetch() ignores the LIMIT clause on GQL queries. + + Args: + limit: Maximum number of results to return. + offset: Optional number of results to skip first; default zero. + rpc: datastore.DatastoreRPC to use for this request. + + Returns: + A list of db.Model instances. There may be fewer than 'limit' + results if there aren't enough results to satisfy the request. + """ + rpc = datastore.GetRpcFromKwargs(kwargs) + + accepted = (int, long) + if not (isinstance(limit, accepted) and isinstance(offset, accepted)): + raise TypeError('Arguments to fetch() must be integers') + if limit < 0 or offset < 0: + raise ValueError('Arguments to fetch() must be >= 0') + + raw_query = self._get_query() + raw = raw_query.Get(limit, offset, rpc=rpc) + + if self._compile: + self._last_raw_query = raw_query + + if self._keys_only: + return raw + else: + if self._model_class is not None: + return [self._model_class.from_entity(e) for e in raw] + else: + return [class_for_kind(e.kind()).from_entity(e) for e in raw] + + def cursor(self): + """Get a serialized cursor for an already executed query. + + The returned cursor effectively lets a future invocation of a similar + query to begin fetching results immediately after the last returned + result from this query invocation. + + Returns: + A base64-encoded serialized cursor. + """ + if not self._compile: + raise AssertionError( + 'Query must be created with compile=True to produce cursors') + try: + return websafe_encode_cursor( + self._last_raw_query.GetCompiledCursor()) + except AttributeError: + raise AssertionError('No cursor available.') + + def with_cursor(self, start_cursor=None, end_cursor=None): + """Set the start and end of this query using serialized cursors. + + Conceptually cursors point to the position between the last result returned + and the next result so running a query with each of the following cursors + combinations will return all results in four chunks with no duplicate + results: + + query.with_cursor(end_cursor=cursor1) + query.with_cursors(cursor1, cursor2) + query.with_cursors(cursor2, cursor3) + query.with_cursors(start_cursor=cursor3) + + For example if the cursors pointed to: + cursor: 1 2 3 + result: a b c d e f g h + + The results returned by these queries would be [a, b], [c, d], [e, f], + [g, h] respectively. + + Cursors are pinned to the position just after the previous result (last + result, exclusive), so if results are inserted or deleted between the time + the cursor was made and these queries are executed, the cursors stay pinned + to these positions. For example: + + delete(b, f, g, h) + put(a1, b1, c1, d1) + cursor: 1(b) 2(d) 3(f) + result: a a1 b1 c c1 d d1 e + + The results returned by these queries would now be: [a, a1], [b1, c, c1, d], + [d1, e], [] respectively. + + Args: + start_cursor: The cursor position at which to start or None + end_cursor: The cursor position at which to end or None + + Returns: + This Query instance, for chaining. + + Raises: + BadValueError when cursor is not valid. + """ + if start_cursor is None: + self._cursor = None + else: + self._cursor = websafe_decode_cursor(start_cursor) + + if end_cursor is None: + self._end_cursor = None + else: + self._end_cursor = websafe_decode_cursor(end_cursor) + + return self + + def __getitem__(self, arg): + """Support for query[index] and query[start:stop]. + + Beware: this ignores the LIMIT clause on GQL queries. + + Args: + arg: Either a single integer, corresponding to the query[index] + syntax, or a Python slice object, corresponding to the + query[start:stop] or query[start:stop:step] syntax. + + Returns: + A single Model instance when the argument is a single integer. + A list of Model instances when the argument is a slice. + """ + if isinstance(arg, slice): + start, stop, step = arg.start, arg.stop, arg.step + if start is None: + start = 0 + if stop is None: + raise ValueError('Open-ended slices are not supported') + if step is None: + step = 1 + if start < 0 or stop < 0 or step != 1: + raise ValueError( + 'Only slices with start>=0, stop>=0, step==1 are supported') + limit = stop - start + if limit < 0: + return [] + return self.fetch(limit, start) + elif isinstance(arg, (int, long)): + if arg < 0: + raise ValueError('Only indices >= 0 are supported') + results = self.fetch(1, arg) + if results: + return results[0] + else: + raise IndexError('The query returned fewer than %d results' % (arg+1)) + else: + raise TypeError('Only integer indices and slices are supported') + + +class _QueryIterator(object): + """Wraps the datastore iterator to return Model instances. + + The datastore returns entities. We wrap the datastore iterator to + return Model instances instead. + """ + + def __init__(self, model_class, datastore_iterator): + """Iterator constructor + + Args: + model_class: Model class from which entities are constructed. + datastore_iterator: Underlying datastore iterator. + """ + self.__model_class = model_class + self.__iterator = datastore_iterator + + def __iter__(self): + """Iterator on self. + + Returns: + Self. + """ + return self + + def next(self): + """Get next Model instance in query results. + + Returns: + Next model instance. + + Raises: + StopIteration when there are no more results in query. + """ + if self.__model_class is not None: + return self.__model_class.from_entity(self.__iterator.next()) + else: + entity = self.__iterator.next() + return class_for_kind(entity.kind()).from_entity(entity) + + +def _normalize_query_parameter(value): + """Make any necessary type conversions to a query parameter. + + The following conversions are made: + - Model instances are converted to Key instances. This is necessary so + that querying reference properties will work. + - datetime.date objects are converted to datetime.datetime objects (see + _date_to_datetime for details on this conversion). This is necessary so + that querying date properties with date objects will work. + - datetime.time objects are converted to datetime.datetime objects (see + _time_to_datetime for details on this conversion). This is necessary so + that querying time properties with time objects will work. + + Args: + value: The query parameter value. + + Returns: + The input value, or a converted value if value matches one of the + conversions specified above. + """ + if isinstance(value, Model): + value = value.key() + if (isinstance(value, datetime.date) and + not isinstance(value, datetime.datetime)): + value = _date_to_datetime(value) + elif isinstance(value, datetime.time): + value = _time_to_datetime(value) + return value + + +class Query(_BaseQuery): + """A Query instance queries over instances of Models. + + You construct a query with a model class, like this: + + class Story(db.Model): + title = db.StringProperty() + date = db.DateTimeProperty() + + query = Query(Story) + + You modify a query with filters and orders like this: + + query.filter('title =', 'Foo') + query.order('-date') + query.ancestor(key_or_model_instance) + + Every query can return an iterator, so you access the results of a query + by iterating over it: + + for story in query: + print story.title + + For convenience, all of the filtering and ordering methods return "self", + so the easiest way to use the query interface is to cascade all filters and + orders in the iterator line like this: + + for story in Query(story).filter('title =', 'Foo').order('-date'): + print story.title + """ + + def __init__(self, model_class=None, keys_only=False, cursor=None, + namespace=None): + """Constructs a query over instances of the given Model. + + Args: + model_class: Model class to build query for. + keys_only: Whether the query should return full entities or only keys. + cursor: A compiled query from which to resume. + namespace: The namespace to use for this query. + """ + super(Query, self).__init__(model_class, keys_only, cursor=cursor, + namespace=namespace) + self.__query_sets = [{}] + self.__orderings = [] + self.__ancestor = None + + def _get_query(self, + _query_class=datastore.Query, + _multi_query_class=datastore.MultiQuery): + queries = [] + for query_set in self.__query_sets: + if self._model_class is not None: + kind = self._model_class.kind() + else: + kind = None + query = _query_class(kind, + query_set, + keys_only=self._keys_only, + compile=self._compile, + cursor=self._cursor, + end_cursor=self._end_cursor, + namespace=self._namespace) + query.Order(*self.__orderings) + if self.__ancestor is not None: + query.Ancestor(self.__ancestor) + queries.append(query) + + if (_query_class != datastore.Query and + _multi_query_class == datastore.MultiQuery): + warnings.warn( + 'Custom _query_class specified without corresponding custom' + ' _query_multi_class. Things will break if you use queries with' + ' the "IN" or "!=" operators.', RuntimeWarning) + if len(queries) > 1: + raise datastore_errors.BadArgumentError( + 'Query requires multiple subqueries to satisfy. If _query_class' + ' is overridden, _multi_query_class must also be overridden.') + elif (_query_class == datastore.Query and + _multi_query_class != datastore.MultiQuery): + raise BadArgumentError('_query_class must also be overridden if' + ' _multi_query_class is overridden.') + + if len(queries) == 1: + return queries[0] + else: + return _multi_query_class(queries, self.__orderings) + + def __filter_disjunction(self, operations, values): + """Add a disjunction of several filters and several values to the query. + + This is implemented by duplicating queries and combining the + results later. + + Args: + operations: a string or list of strings. Each string contains a + property name and an operator to filter by. The operators + themselves must not require multiple queries to evaluate + (currently, this means that 'in' and '!=' are invalid). + + values: a value or list of filter values, normalized by + _normalize_query_parameter. + """ + if not isinstance(operations, (list, tuple)): + operations = [operations] + if not isinstance(values, (list, tuple)): + values = [values] + + new_query_sets = [] + for operation in operations: + if operation.lower().endswith('in') or operation.endswith('!='): + raise BadQueryError('Cannot use "in" or "!=" in a disjunction.') + for query_set in self.__query_sets: + for value in values: + new_query_set = copy.deepcopy(query_set) + datastore._AddOrAppend(new_query_set, operation, value) + new_query_sets.append(new_query_set) + self.__query_sets = new_query_sets + + def filter(self, property_operator, value): + """Add filter to query. + + Args: + property_operator: string with the property and operator to filter by. + value: the filter value. + + Returns: + Self to support method chaining. + + Raises: + PropertyError if invalid property is provided. + """ + match = _FILTER_REGEX.match(property_operator) + prop = match.group(1) + if match.group(3) is not None: + operator = match.group(3) + else: + operator = '==' + + if self._model_class is None: + if prop != datastore_types._KEY_SPECIAL_PROPERTY: + raise BadQueryError( + 'Only %s filters are allowed on kindless queries.' % + datastore_types._KEY_SPECIAL_PROPERTY) + elif prop in self._model_class._unindexed_properties: + raise PropertyError('Property \'%s\' is not indexed' % prop) + + if operator.lower() == 'in': + if self._keys_only: + raise BadQueryError('Keys only queries do not support IN filters.') + elif not isinstance(value, (list, tuple)): + raise BadValueError('Argument to the "in" operator must be a list') + values = [_normalize_query_parameter(v) for v in value] + self.__filter_disjunction(prop + ' =', values) + else: + if isinstance(value, (list, tuple)): + raise BadValueError('Filtering on lists is not supported') + if operator == '!=': + if self._keys_only: + raise BadQueryError('Keys only queries do not support != filters.') + self.__filter_disjunction([prop + ' <', prop + ' >'], + _normalize_query_parameter(value)) + else: + value = _normalize_query_parameter(value) + for query_set in self.__query_sets: + datastore._AddOrAppend(query_set, property_operator, value) + + return self + + def order(self, property): + """Set order of query result. + + To use descending order, prepend '-' (minus) to the property + name, e.g., '-date' rather than 'date'. + + Args: + property: Property to sort on. + + Returns: + Self to support method chaining. + + Raises: + PropertyError if invalid property is provided. + """ + if property.startswith('-'): + property = property[1:] + order = datastore.Query.DESCENDING + else: + order = datastore.Query.ASCENDING + + if self._model_class is None: + if (property != datastore_types._KEY_SPECIAL_PROPERTY or + order != datastore.Query.ASCENDING): + raise BadQueryError( + 'Only %s ascending orders are supported on kindless queries' % + datastore_types._KEY_SPECIAL_PROPERTY) + else: + if not issubclass(self._model_class, Expando): + if (property not in self._model_class.properties() and + property not in datastore_types._SPECIAL_PROPERTIES): + raise PropertyError('Invalid property name \'%s\'' % property) + + if property in self._model_class._unindexed_properties: + raise PropertyError('Property \'%s\' is not indexed' % property) + + self.__orderings.append((property, order)) + return self + + def ancestor(self, ancestor): + """Sets an ancestor for this query. + + This restricts the query to only return results that descend from + a given model instance. In other words, all of the results will + have the ancestor as their parent, or parent's parent, etc. The + ancestor itself is also a possible result! + + Args: + ancestor: Model or Key (that has already been saved) + + Returns: + Self to support method chaining. + + Raises: + TypeError if the argument isn't a Key or Model; NotSavedError + if it is, but isn't saved yet. + """ + if isinstance(ancestor, datastore.Key): + if ancestor.has_id_or_name(): + self.__ancestor = ancestor + else: + raise NotSavedError() + elif isinstance(ancestor, Model): + if ancestor.has_key(): + self.__ancestor = ancestor.key() + else: + raise NotSavedError() + else: + raise TypeError('ancestor should be Key or Model') + return self + + +class GqlQuery(_BaseQuery): + """A Query class that uses GQL query syntax instead of .filter() etc.""" + + def __init__(self, query_string, *args, **kwds): + """Constructor. + + Args: + query_string: Properly formatted GQL query string. + *args: Positional arguments used to bind numeric references in the query. + **kwds: Dictionary-based arguments for named references. + + Raises: + PropertyError if the query filters or sorts on a property that's not + indexed. + """ + from google.appengine.ext import gql + app = kwds.pop('_app', None) + namespace = None + if isinstance(app, tuple): + if len(app) != 2: + raise BadArgumentError('_app must have 2 values if type is tuple.') + app, namespace = app + + self._proto_query = gql.GQL(query_string, _app=app, namespace=namespace) + if self._proto_query._entity is not None: + model_class = class_for_kind(self._proto_query._entity) + else: + model_class = None + super(GqlQuery, self).__init__(model_class, + keys_only=self._proto_query._keys_only) + + if model_class is not None: + for property, unused in (self._proto_query.filters().keys() + + self._proto_query.orderings()): + if property in model_class._unindexed_properties: + raise PropertyError('Property \'%s\' is not indexed' % property) + + self.bind(*args, **kwds) + + def bind(self, *args, **kwds): + """Bind arguments (positional or keyword) to the query. + + Note that you can also pass arguments directly to the query + constructor. Each time you call bind() the previous set of + arguments is replaced with the new set. This is useful because + the hard work in in parsing the query; so if you expect to be + using the same query with different sets of arguments, you should + hold on to the GqlQuery() object and call bind() on it each time. + + Args: + *args: Positional arguments used to bind numeric references in the query. + **kwds: Dictionary-based arguments for named references. + """ + self._args = [] + for arg in args: + self._args.append(_normalize_query_parameter(arg)) + self._kwds = {} + for name, arg in kwds.iteritems(): + self._kwds[name] = _normalize_query_parameter(arg) + + def run(self, **kwargs): + """Iterator for this query that handles the LIMIT clause property. + + If the GQL query string contains a LIMIT clause, this function fetches + all results before returning an iterator. Otherwise results are retrieved + in batches by the iterator. + + Args: + rpc: datastore.DatastoreRPC to use for this request. + + Returns: + Iterator for this query. + """ + if self._proto_query.limit() >= 0: + return iter(self.fetch(limit=self._proto_query.limit(), + offset=self._proto_query.offset(), + **kwargs)) + else: + results = _BaseQuery.run(self, **kwargs) + try: + for _ in xrange(self._proto_query.offset()): + results.next() + except StopIteration: + pass + + return results + + def _get_query(self): + return self._proto_query.Bind(self._args, self._kwds, + self._cursor, self._end_cursor) + + +class UnindexedProperty(Property): + """A property that isn't indexed by either built-in or composite indices. + + TextProperty and BlobProperty derive from this class. + """ + def __init__(self, *args, **kwds): + """Construct property. See the Property class for details. + + Raises: + ConfigurationError if indexed=True. + """ + self._require_parameter(kwds, 'indexed', False) + kwds['indexed'] = True + super(UnindexedProperty, self).__init__(*args, **kwds) + + def validate(self, value): + """Validate property. + + Returns: + A valid value. + + Raises: + BadValueError if property is not an instance of data_type. + """ + if value is not None and not isinstance(value, self.data_type): + try: + value = self.data_type(value) + except TypeError, err: + raise BadValueError('Property %s must be convertible ' + 'to a %s instance (%s)' % + (self.name, self.data_type.__name__, err)) + value = super(UnindexedProperty, self).validate(value) + if value is not None and not isinstance(value, self.data_type): + raise BadValueError('Property %s must be a %s instance' % + (self.name, self.data_type.__name__)) + return value + + +class TextProperty(UnindexedProperty): + """A string that can be longer than 500 bytes.""" + + data_type = Text + + +class StringProperty(Property): + """A textual property, which can be multi- or single-line.""" + + def __init__(self, verbose_name=None, multiline=False, **kwds): + """Construct string property. + + Args: + verbose_name: Verbose name is always first parameter. + multi-line: Carriage returns permitted in property. + """ + super(StringProperty, self).__init__(verbose_name, **kwds) + self.multiline = multiline + + def validate(self, value): + """Validate string property. + + Returns: + A valid value. + + Raises: + BadValueError if property is not multi-line but value is. + """ + value = super(StringProperty, self).validate(value) + if value is not None and not isinstance(value, basestring): + raise BadValueError( + 'Property %s must be a str or unicode instance, not a %s' + % (self.name, type(value).__name__)) + if not self.multiline and value and value.find('\n') != -1: + raise BadValueError('Property %s is not multi-line' % self.name) + return value + + data_type = basestring + + +class _CoercingProperty(Property): + """A Property subclass that extends validate() to coerce to self.data_type.""" + + def validate(self, value): + """Coerce values (except None) to self.data_type. + + Args: + value: The value to be validated and coerced. + + Returns: + The coerced and validated value. It is guaranteed that this is + either None or an instance of self.data_type; otherwise an exception + is raised. + + Raises: + BadValueError if the value could not be validated or coerced. + """ + value = super(_CoercingProperty, self).validate(value) + if value is not None and not isinstance(value, self.data_type): + value = self.data_type(value) + return value + + +class CategoryProperty(_CoercingProperty): + """A property whose values are Category instances.""" + + data_type = Category + + +class LinkProperty(_CoercingProperty): + """A property whose values are Link instances.""" + + def validate(self, value): + value = super(LinkProperty, self).validate(value) + if value is not None: + scheme, netloc, path, query, fragment = urlparse.urlsplit(value) + if not scheme or not netloc: + raise BadValueError('Property %s must be a full URL (\'%s\')' % + (self.name, value)) + return value + + data_type = Link + +URLProperty = LinkProperty + + +class EmailProperty(_CoercingProperty): + """A property whose values are Email instances.""" + + data_type = Email + + +class GeoPtProperty(_CoercingProperty): + """A property whose values are GeoPt instances.""" + + data_type = GeoPt + + +class IMProperty(_CoercingProperty): + """A property whose values are IM instances.""" + + data_type = IM + + +class PhoneNumberProperty(_CoercingProperty): + """A property whose values are PhoneNumber instances.""" + + data_type = PhoneNumber + + +class PostalAddressProperty(_CoercingProperty): + """A property whose values are PostalAddress instances.""" + + data_type = PostalAddress + + +class BlobProperty(UnindexedProperty): + """A byte string that can be longer than 500 bytes.""" + + data_type = Blob + + +class ByteStringProperty(Property): + """A short (<=500 bytes) byte string. + + This type should be used for short binary values that need to be indexed. If + you do not require indexing (regardless of length), use BlobProperty instead. + """ + + def validate(self, value): + """Validate ByteString property. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'ByteString'. + """ + if value is not None and not isinstance(value, ByteString): + try: + value = ByteString(value) + except TypeError, err: + raise BadValueError('Property %s must be convertible ' + 'to a ByteString instance (%s)' % (self.name, err)) + value = super(ByteStringProperty, self).validate(value) + if value is not None and not isinstance(value, ByteString): + raise BadValueError('Property %s must be a ByteString instance' + % self.name) + return value + + data_type = ByteString + + +class DateTimeProperty(Property): + """The base class of all of our date/time properties. + + We handle common operations, like converting between time tuples and + datetime instances. + """ + + def __init__(self, verbose_name=None, auto_now=False, auto_now_add=False, + **kwds): + """Construct a DateTimeProperty + + Args: + verbose_name: Verbose name is always first parameter. + auto_now: Date/time property is updated with the current time every time + it is saved to the datastore. Useful for properties that want to track + the modification time of an instance. + auto_now_add: Date/time is set to the when its instance is created. + Useful for properties that record the creation time of an entity. + """ + super(DateTimeProperty, self).__init__(verbose_name, **kwds) + self.auto_now = auto_now + self.auto_now_add = auto_now_add + + def validate(self, value): + """Validate datetime. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'datetime'. + """ + value = super(DateTimeProperty, self).validate(value) + if value and not isinstance(value, self.data_type): + raise BadValueError('Property %s must be a %s' % + (self.name, self.data_type.__name__)) + return value + + def default_value(self): + """Default value for datetime. + + Returns: + value of now() as appropriate to the date-time instance if auto_now + or auto_now_add is set, else user configured default value implementation. + """ + if self.auto_now or self.auto_now_add: + return self.now() + return Property.default_value(self) + + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + Returns: + now() as appropriate to the date-time instance in the odd case where + auto_now is set to True, else the default implementation. + """ + if self.auto_now: + return self.now() + else: + return super(DateTimeProperty, + self).get_value_for_datastore(model_instance) + + data_type = datetime.datetime + + @staticmethod + def now(): + """Get now as a full datetime value. + + Returns: + 'now' as a whole timestamp, including both time and date. + """ + return datetime.datetime.now() + + +def _date_to_datetime(value): + """Convert a date to a datetime for datastore storage. + + Args: + value: A datetime.date object. + + Returns: + A datetime object with time set to 0:00. + """ + assert isinstance(value, datetime.date) + return datetime.datetime(value.year, value.month, value.day) + + +def _time_to_datetime(value): + """Convert a time to a datetime for datastore storage. + + Args: + value: A datetime.time object. + + Returns: + A datetime object with date set to 1970-01-01. + """ + assert isinstance(value, datetime.time) + return datetime.datetime(1970, 1, 1, + value.hour, value.minute, value.second, + value.microsecond) + + +class DateProperty(DateTimeProperty): + """A date property, which stores a date without a time.""" + + + @staticmethod + def now(): + """Get now as a date datetime value. + + Returns: + 'date' part of 'now' only. + """ + return datetime.datetime.now().date() + + def validate(self, value): + """Validate date. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'date', + or if it is an instance of 'datetime' (which is a subclass + of 'date', but for all practical purposes a different type). + """ + value = super(DateProperty, self).validate(value) + if isinstance(value, datetime.datetime): + raise BadValueError('Property %s must be a %s, not a datetime' % + (self.name, self.data_type.__name__)) + return value + + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + We retrieve a datetime.date from the model instance and return a + datetime.datetime instance with the time set to zero. + + See base class method documentation for details. + """ + value = super(DateProperty, self).get_value_for_datastore(model_instance) + if value is not None: + assert isinstance(value, datetime.date) + value = _date_to_datetime(value) + return value + + def make_value_from_datastore(self, value): + """Native representation of this property. + + We receive a datetime.datetime retrieved from the entity and return + a datetime.date instance representing its date portion. + + See base class method documentation for details. + """ + if value is not None: + assert isinstance(value, datetime.datetime) + value = value.date() + return value + + data_type = datetime.date + + +class TimeProperty(DateTimeProperty): + """A time property, which stores a time without a date.""" + + + @staticmethod + def now(): + """Get now as a time datetime value. + + Returns: + 'time' part of 'now' only. + """ + return datetime.datetime.now().time() + + def empty(self, value): + """Is time property empty. + + "0:0" (midnight) is not an empty value. + + Returns: + True if value is None, else False. + """ + return value is None + + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + We retrieve a datetime.time from the model instance and return a + datetime.datetime instance with the date set to 1/1/1970. + + See base class method documentation for details. + """ + value = super(TimeProperty, self).get_value_for_datastore(model_instance) + if value is not None: + assert isinstance(value, datetime.time), repr(value) + value = _time_to_datetime(value) + return value + + def make_value_from_datastore(self, value): + """Native representation of this property. + + We receive a datetime.datetime retrieved from the entity and return + a datetime.date instance representing its time portion. + + See base class method documentation for details. + """ + if value is not None: + assert isinstance(value, datetime.datetime) + value = value.time() + return value + + data_type = datetime.time + + +class IntegerProperty(Property): + """An integer property.""" + + def validate(self, value): + """Validate integer property. + + Returns: + A valid value. + + Raises: + BadValueError if value is not an integer or long instance. + """ + value = super(IntegerProperty, self).validate(value) + if value is None: + return value + if not isinstance(value, (int, long)) or isinstance(value, bool): + raise BadValueError('Property %s must be an int or long, not a %s' + % (self.name, type(value).__name__)) + if value < -0x8000000000000000 or value > 0x7fffffffffffffff: + raise BadValueError('Property %s must fit in 64 bits' % self.name) + return value + + data_type = int + + def empty(self, value): + """Is integer property empty. + + 0 is not an empty value. + + Returns: + True if value is None, else False. + """ + return value is None + + +class RatingProperty(_CoercingProperty, IntegerProperty): + """A property whose values are Rating instances.""" + + data_type = Rating + + +class FloatProperty(Property): + """A float property.""" + + def validate(self, value): + """Validate float. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'float'. + """ + value = super(FloatProperty, self).validate(value) + if value is not None and not isinstance(value, float): + raise BadValueError('Property %s must be a float' % self.name) + return value + + data_type = float + + def empty(self, value): + """Is float property empty. + + 0.0 is not an empty value. + + Returns: + True if value is None, else False. + """ + return value is None + + +class BooleanProperty(Property): + """A boolean property.""" + + def validate(self, value): + """Validate boolean. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'bool'. + """ + value = super(BooleanProperty, self).validate(value) + if value is not None and not isinstance(value, bool): + raise BadValueError('Property %s must be a bool' % self.name) + return value + + data_type = bool + + def empty(self, value): + """Is boolean property empty. + + False is not an empty value. + + Returns: + True if value is None, else False. + """ + return value is None + + +class UserProperty(Property): + """A user property.""" + + def __init__(self, + verbose_name=None, + name=None, + required=False, + validator=None, + choices=None, + auto_current_user=False, + auto_current_user_add=False, + indexed=True): + """Initializes this Property with the given options. + + Note: this does *not* support the 'default' keyword argument. + Use auto_current_user_add=True instead. + + Args: + verbose_name: User friendly name of property. + name: Storage name for property. By default, uses attribute name + as it is assigned in the Model sub-class. + required: Whether property is required. + validator: User provided method used for validation. + choices: User provided set of valid property values. + auto_current_user: If true, the value is set to the current user + each time the entity is written to the datastore. + auto_current_user_add: If true, the value is set to the current user + the first time the entity is written to the datastore. + indexed: Whether property is indexed. + """ + super(UserProperty, self).__init__(verbose_name, name, + required=required, + validator=validator, + choices=choices, + indexed=indexed) + self.auto_current_user = auto_current_user + self.auto_current_user_add = auto_current_user_add + + def validate(self, value): + """Validate user. + + Returns: + A valid value. + + Raises: + BadValueError if property is not instance of 'User'. + """ + value = super(UserProperty, self).validate(value) + if value is not None and not isinstance(value, users.User): + raise BadValueError('Property %s must be a User' % self.name) + return value + + def default_value(self): + """Default value for user. + + Returns: + Value of users.get_current_user() if auto_current_user or + auto_current_user_add is set; else None. (But *not* the default + implementation, since we don't support the 'default' keyword + argument.) + """ + if self.auto_current_user or self.auto_current_user_add: + return users.get_current_user() + return None + + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + Returns: + Value of users.get_current_user() if auto_current_user is set; + else the default implementation. + """ + if self.auto_current_user: + return users.get_current_user() + return super(UserProperty, self).get_value_for_datastore(model_instance) + + data_type = users.User + + +class ListProperty(Property): + """A property that stores a list of things. + + This is a parameterized property; the parameter must be a valid + non-list data type, and all items must conform to this type. + """ + + def __init__(self, item_type, verbose_name=None, default=None, **kwds): + """Construct ListProperty. + + Args: + item_type: Type for the list items; must be one of the allowed property + types. + verbose_name: Optional verbose name. + default: Optional default value; if omitted, an empty list is used. + **kwds: Optional additional keyword arguments, passed to base class. + + Note that the only permissible value for 'required' is True. + """ + if item_type is str: + item_type = basestring + if not isinstance(item_type, type): + raise TypeError('Item type should be a type object') + if item_type not in _ALLOWED_PROPERTY_TYPES: + raise ValueError('Item type %s is not acceptable' % item_type.__name__) + if issubclass(item_type, (Blob, Text)): + self._require_parameter(kwds, 'indexed', False) + kwds['indexed'] = True + self._require_parameter(kwds, 'required', True) + if default is None: + default = [] + self.item_type = item_type + super(ListProperty, self).__init__(verbose_name, + default=default, + **kwds) + + def validate(self, value): + """Validate list. + + Returns: + A valid value. + + Raises: + BadValueError if property is not a list whose items are instances of + the item_type given to the constructor. + """ + value = super(ListProperty, self).validate(value) + if value is not None: + if not isinstance(value, list): + raise BadValueError('Property %s must be a list' % self.name) + + value = self.validate_list_contents(value) + return value + + def validate_list_contents(self, value): + """Validates that all items in the list are of the correct type. + + Returns: + The validated list. + + Raises: + BadValueError if the list has items are not instances of the + item_type given to the constructor. + """ + if self.item_type in (int, long): + item_type = (int, long) + else: + item_type = self.item_type + + for item in value: + if not isinstance(item, item_type): + if item_type == (int, long): + raise BadValueError('Items in the %s list must all be integers.' % + self.name) + else: + raise BadValueError( + 'Items in the %s list must all be %s instances' % + (self.name, self.item_type.__name__)) + return value + + def empty(self, value): + """Is list property empty. + + [] is not an empty value. + + Returns: + True if value is None, else false. + """ + return value is None + + data_type = list + + def default_value(self): + """Default value for list. + + Because the property supplied to 'default' is a static value, + that value must be shallow copied to prevent all fields with + default values from sharing the same instance. + + Returns: + Copy of the default value. + """ + return list(super(ListProperty, self).default_value()) + + def get_value_for_datastore(self, model_instance): + """Get value from property to send to datastore. + + Returns: + validated list appropriate to save in the datastore. + """ + value = self.validate_list_contents( + super(ListProperty, self).get_value_for_datastore(model_instance)) + if self.validator: + self.validator(value) + return value + + +class StringListProperty(ListProperty): + """A property that stores a list of strings. + + A shorthand for the most common type of ListProperty. + """ + + def __init__(self, verbose_name=None, default=None, **kwds): + """Construct StringListProperty. + + Args: + verbose_name: Optional verbose name. + default: Optional default value; if omitted, an empty list is used. + **kwds: Optional additional keyword arguments, passed to ListProperty(). + """ + super(StringListProperty, self).__init__(basestring, + verbose_name=verbose_name, + default=default, + **kwds) + + +class ReferenceProperty(Property): + """A property that represents a many-to-one reference to another model. + + For example, a reference property in model A that refers to model B forms + a many-to-one relationship from A to B: every instance of A refers to a + single B instance, and every B instance can have many A instances refer + to it. + """ + + def __init__(self, + reference_class=None, + verbose_name=None, + collection_name=None, + **attrs): + """Construct ReferenceProperty. + + Args: + reference_class: Which model class this property references. + verbose_name: User friendly name of property. + collection_name: If provided, alternate name of collection on + reference_class to store back references. Use this to allow + a Model to have multiple fields which refer to the same class. + """ + super(ReferenceProperty, self).__init__(verbose_name, **attrs) + + self.collection_name = collection_name + + if reference_class is None: + reference_class = Model + if not ((isinstance(reference_class, type) and + issubclass(reference_class, Model)) or + reference_class is _SELF_REFERENCE): + raise KindError('reference_class must be Model or _SELF_REFERENCE') + self.reference_class = self.data_type = reference_class + + def __property_config__(self, model_class, property_name): + """Loads all of the references that point to this model. + + We need to do this to create the ReverseReferenceProperty properties for + this model and create the _set attributes on the referenced + model, e.g.: + + class Story(db.Model): + title = db.StringProperty() + class Comment(db.Model): + story = db.ReferenceProperty(Story) + story = Story.get(id) + print [c for c in story.comment_set] + + In this example, the comment_set property was created based on the reference + from Comment to Story (which is inherently one to many). + + Args: + model_class: Model class which will have its reference properties + initialized. + property_name: Name of property being configured. + + Raises: + DuplicatePropertyError if referenced class already has the provided + collection name as a property. + """ + super(ReferenceProperty, self).__property_config__(model_class, + property_name) + + if self.reference_class is _SELF_REFERENCE: + self.reference_class = self.data_type = model_class + + if self.collection_name is None: + self.collection_name = '%s_set' % (model_class.__name__.lower()) + existing_prop = getattr(self.reference_class, self.collection_name, None) + if existing_prop is not None: + if not (isinstance(existing_prop, _ReverseReferenceProperty) and + existing_prop._prop_name == property_name and + existing_prop._model.__name__ == model_class.__name__ and + existing_prop._model.__module__ == model_class.__module__): + raise DuplicatePropertyError('Class %s already has property %s ' + % (self.reference_class.__name__, + self.collection_name)) + setattr(self.reference_class, + self.collection_name, + _ReverseReferenceProperty(model_class, property_name)) + + def __get__(self, model_instance, model_class): + """Get reference object. + + This method will fetch unresolved entities from the datastore if + they are not already loaded. + + Returns: + ReferenceProperty to Model object if property is set, else None. + """ + if model_instance is None: + return self + if hasattr(model_instance, self.__id_attr_name()): + reference_id = getattr(model_instance, self.__id_attr_name()) + else: + reference_id = None + if reference_id is not None: + resolved = getattr(model_instance, self.__resolved_attr_name()) + if resolved is not None: + return resolved + else: + instance = get(reference_id) + if instance is None: + raise Error('ReferenceProperty failed to be resolved') + setattr(model_instance, self.__resolved_attr_name(), instance) + return instance + else: + return None + + def __set__(self, model_instance, value): + """Set reference.""" + value = self.validate(value) + if value is not None: + if isinstance(value, datastore.Key): + setattr(model_instance, self.__id_attr_name(), value) + setattr(model_instance, self.__resolved_attr_name(), None) + else: + setattr(model_instance, self.__id_attr_name(), value.key()) + setattr(model_instance, self.__resolved_attr_name(), value) + else: + setattr(model_instance, self.__id_attr_name(), None) + setattr(model_instance, self.__resolved_attr_name(), None) + + def get_value_for_datastore(self, model_instance): + """Get key of reference rather than reference itself.""" + return getattr(model_instance, self.__id_attr_name()) + + def validate(self, value): + """Validate reference. + + Returns: + A valid value. + + Raises: + BadValueError for the following reasons: + - Value is not saved. + - Object not of correct model type for reference. + """ + if isinstance(value, datastore.Key): + return value + + if value is not None and not value.has_key(): + raise BadValueError( + '%s instance must have a complete key before it can be stored as a ' + 'reference' % self.reference_class.kind()) + + value = super(ReferenceProperty, self).validate(value) + + if value is not None and not isinstance(value, self.reference_class): + raise KindError('Property %s must be an instance of %s' % + (self.name, self.reference_class.kind())) + + return value + + def __id_attr_name(self): + """Get attribute of referenced id. + + Returns: + Attribute where to store id of referenced entity. + """ + return self._attr_name() + + def __resolved_attr_name(self): + """Get attribute of resolved attribute. + + The resolved attribute is where the actual loaded reference instance is + stored on the referring model instance. + + Returns: + Attribute name of where to store resolved reference model instance. + """ + return '_RESOLVED' + self._attr_name() + + +Reference = ReferenceProperty + + +def SelfReferenceProperty(verbose_name=None, collection_name=None, **attrs): + """Create a self reference. + + Function for declaring a self referencing property on a model. + + Example: + class HtmlNode(db.Model): + parent = db.SelfReferenceProperty('Parent', 'children') + + Args: + verbose_name: User friendly name of property. + collection_name: Name of collection on model. + + Raises: + ConfigurationError if reference_class provided as parameter. + """ + if 'reference_class' in attrs: + raise ConfigurationError( + 'Do not provide reference_class to self-reference.') + return ReferenceProperty(_SELF_REFERENCE, + verbose_name, + collection_name, + **attrs) + + +SelfReference = SelfReferenceProperty + + +class _ReverseReferenceProperty(Property): + """The inverse of the Reference property above. + + We construct reverse references automatically for the model to which + the Reference property is pointing to create the one-to-many property for + that model. For example, if you put a Reference property in model A that + refers to model B, we automatically create a _ReverseReference property in + B called a_set that can fetch all of the model A instances that refer to + that instance of model B. + """ + + def __init__(self, model, prop): + """Constructor for reverse reference. + + Constructor does not take standard values of other property types. + + Args: + model: Model class that this property is a collection of. + property: Name of foreign property on referred model that points back + to this properties entity. + """ + self.__model = model + self.__property = prop + + @property + def _model(self): + """Internal helper to access the model class, read-only.""" + return self.__model + + @property + def _prop_name(self): + """Internal helper to access the property name, read-only.""" + return self.__property + + def __get__(self, model_instance, model_class): + """Fetches collection of model instances of this collection property.""" + if model_instance is not None: + query = Query(self.__model) + return query.filter(self.__property + ' =', model_instance.key()) + else: + return self + + def __set__(self, model_instance, value): + """Not possible to set a new collection.""" + raise BadValueError('Virtual property is read-only') + + +class ComputedProperty(Property): + """Property used for creating properties derived from other values. + + Certain attributes should never be set by users but automatically + calculated at run-time from other values of the same entity. These + values are implemented as persistent properties because they provide + useful search keys. + + A computed property behaves the same as normal properties except that + you may not set values on them. Attempting to do so raises + db.DerivedPropertyError which db.Model knows to ignore during entity + loading time. Whenever getattr is used for the property + the value is recaclulated. This happens when the model calls + get_value_for_datastore on the property. + + Example: + + import string + + class Person(Model): + + name = StringProperty(required=True) + + @db.ComputedProperty + def lower_case_name(self): + return self.name.lower() + + # Find all people regardless of case used in name. + Person.gql('WHERE lower_case_name=:1' % name_to_search_for.lower()) + """ + + def __init__(self, value_function, indexed=True): + """Constructor. + + Args: + value_function: Callable f(model_instance) -> value used to derive + persistent property value for storage in datastore. + indexed: Whether or not the attribute should be indexed. + """ + super(ComputedProperty, self).__init__(indexed=indexed) + self.__value_function = value_function + + def __set__(self, *args): + """Disallow setting this value. + + Raises: + DerivedPropertyError when developer attempts to set attribute manually. + Model knows to ignore this exception when getting from datastore. + """ + raise DerivedPropertyError( + 'Computed property %s cannot be set.' % self.name) + + def __get__(self, model_instance, model_class): + """Derive property value. + + Args: + model_instance: Instance to derive property for in bound method case, + else None. + model_class: Model class associated with this property descriptor. + + Returns: + Result of calling self.__value_funcion as provided by property + constructor. + """ + if model_instance is None: + return self + return self.__value_function(model_instance) + + +run_in_transaction = datastore.RunInTransaction +run_in_transaction_custom_retries = datastore.RunInTransactionCustomRetries + +RunInTransaction = run_in_transaction +RunInTransactionCustomRetries = run_in_transaction_custom_retries + +is_in_transaction = datastore.IsInTransaction diff --git a/google_appengine/google/appengine/ext/db/__init__.pyc b/google_appengine/google/appengine/ext/db/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12407cf6ff0fe76c69b21435c857c0f87e07d89f GIT binary patch literal 123459 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/db/djangoforms.py b/google_appengine/google/appengine/ext/db/djangoforms.py new file mode 100755 index 0000000..e3d5143 --- /dev/null +++ b/google_appengine/google/appengine/ext/db/djangoforms.py @@ -0,0 +1,904 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Support for creating Django (new) forms from Datastore data models. + +This is our best shot at supporting as much of Django as possible: you +won't be able to use Django's db package, but you can use our +db package instead, and create Django forms from it, either fully +automatically, or with overrides. + +Note, you should not import these classes from this module. Importing +this module patches the classes in place, and you should continue to +import them from google.appengine.db. + +Some of the code here is strongly inspired by Django's own ModelForm +class (new in Django 0.97). Our code also supports Django 0.96 (so as +to be maximally compatible). Note that our API is always similar to +Django 0.97's API, even when used with Django 0.96 (which uses a +different API, chiefly form_for_model()). + +Terminology notes: + - forms: always refers to the Django newforms subpackage + - field: always refers to a Django forms.Field instance + - property: always refers to a db.Property instance + +Mapping between properties and fields: + ++====================+===================+==============+====================+ +| Property subclass | Field subclass | datatype | widget; notes | ++====================+===================+==============+====================+ +| StringProperty | CharField | unicode | Textarea | +| | | | if multiline | ++--------------------+-------------------+--------------+--------------------+ +| TextProperty | CharField | unicode | Textarea | ++--------------------+-------------------+--------------+--------------------+ +| BlobProperty | FileField | str | skipped in v0.96 | ++--------------------+-------------------+--------------+--------------------+ +| DateTimeProperty | DateTimeField | datetime | skipped | +| | | | if auto_now[_add] | ++--------------------+-------------------+--------------+--------------------+ +| DateProperty | DateField | date | ditto | ++--------------------+-------------------+--------------+--------------------+ +| TimeProperty | TimeField | time | ditto | ++--------------------+-------------------+--------------+--------------------+ +| IntegerProperty | IntegerField | int or long | | ++--------------------+-------------------+--------------+--------------------+ +| FloatProperty | FloatField | float | CharField in v0.96 | ++--------------------+-------------------+--------------+--------------------+ +| BooleanProperty | BooleanField | bool | | ++--------------------+-------------------+--------------+--------------------+ +| UserProperty | CharField | users.User | | ++--------------------+-------------------+--------------+--------------------+ +| StringListProperty | CharField | list of str | Textarea | ++--------------------+-------------------+--------------+--------------------+ +| LinkProperty | URLField | str | | ++--------------------+-------------------+--------------+--------------------+ +| ReferenceProperty | ModelChoiceField* | db.Model | | ++--------------------+-------------------+--------------+--------------------+ +| _ReverseReferenceP.| None | | always skipped | ++====================+===================+==============+====================+ + +Notes: +*: this Field subclasses is defined by us, not in Django. +""" + + + +import itertools +import logging + + +import django.core.exceptions +import django.utils.datastructures + +try: + from django import newforms as forms +except ImportError: + from django import forms + +try: + from django.utils.translation import ugettext_lazy as _ +except ImportError: + pass + +from google.appengine.api import users +from google.appengine.ext import db + + + + +def monkey_patch(name, bases, namespace): + """A 'metaclass' for adding new methods to an existing class. + + This shouldn't be used to override existing methods. However, + because loading this module (like loading any module) should be + idempotent, we don't assert that. + + Usage example: + + class PatchClass(TargetClass): + __metaclass__ = monkey_patch + def foo(self, ...): ... + def bar(self, ...): ... + + This is equivalent to: + + def foo(self, ...): ... + def bar(self, ...): ... + TargetClass.foo = foo + TargetClass.bar = bar + PatchClass = TargetClass + + Note that PatchClass becomes an alias for TargetClass; by convention + it is recommended to give PatchClass the same name as TargetClass. + """ + + assert len(bases) == 1, 'Exactly one base class is required' + base = bases[0] + for name, value in namespace.iteritems(): + if name not in ('__metaclass__', '__module__'): + setattr(base, name, value) + return base + + + + +class Property(db.Property): + __metaclass__ = monkey_patch + + def get_form_field(self, form_class=forms.CharField, **kwargs): + """Return a Django form field appropriate for this property. + + Args: + form_class: a forms.Field subclass, default forms.CharField + + Additional keyword arguments are passed to the form_class constructor, + with certain defaults: + required: self.required + label: prettified self.verbose_name, if not None + widget: a forms.Select instance if self.choices is non-empty + initial: self.default, if not None + + Returns: + A fully configured instance of form_class, or None if no form + field should be generated for this property. + """ + defaults = {'required': self.required} + if self.verbose_name: + defaults['label'] = self.verbose_name.capitalize().replace('_', ' ') + if self.choices: + choices = [] + if not self.required or (self.default is None and + 'initial' not in kwargs): + choices.append(('', '---------')) + for choice in self.choices: + choices.append((str(choice), unicode(choice))) + defaults['widget'] = forms.Select(choices=choices) + if self.default is not None: + defaults['initial'] = self.default + defaults.update(kwargs) + return form_class(**defaults) + + def get_value_for_form(self, instance): + """Extract the property value from the instance for use in a form. + + Override this to do a property- or field-specific type conversion. + + Args: + instance: a db.Model instance + + Returns: + The property's value extracted from the instance, possibly + converted to a type suitable for a form field; possibly None. + + By default this returns the instance attribute's value unchanged. + """ + return getattr(instance, self.name) + + def make_value_from_form(self, value): + """Convert a form value to a property value. + + Override this to do a property- or field-specific type conversion. + + Args: + value: the cleaned value retrieved from the form field + + Returns: + A value suitable for assignment to a model instance's property; + possibly None. + + By default this converts the value to self.data_type if it + isn't already an instance of that type, except if the value is + empty, in which case we return None. + """ + if value in (None, ''): + return None + if not isinstance(value, self.data_type): + value = self.data_type(value) + return value + + +class UserProperty(db.UserProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a User property. + + This defaults to a forms.EmailField instance, except if auto_current_user or + auto_current_user_add is set, in which case None is returned, as such + 'auto' fields should not be rendered as part of the form. + """ + if self.auto_current_user or self.auto_current_user_add: + return None + defaults = {'form_class': forms.EmailField} + defaults.update(kwargs) + return super(UserProperty, self).get_form_field(**defaults) + + def get_value_for_form(self, instance): + """Extract the property value from the instance for use in a form. + + This returns the email address of the User. + """ + value = super(UserProperty, self).get_value_for_form(instance) + if not value: + return None + return value.email() + + +class StringProperty(db.StringProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a string property. + + This sets the widget default to forms.Textarea if the property's + multiline attribute is set. + """ + defaults = {} + if self.multiline: + defaults['widget'] = forms.Textarea + defaults.update(kwargs) + return super(StringProperty, self).get_form_field(**defaults) + + +class TextProperty(db.TextProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a text property. + + This sets the widget default to forms.Textarea. + """ + defaults = {'widget': forms.Textarea} + defaults.update(kwargs) + return super(TextProperty, self).get_form_field(**defaults) + + +class BlobProperty(db.BlobProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a blob property. + + This defaults to a forms.FileField instance when using Django 0.97 + or later. For 0.96 this returns None, as file uploads are not + really supported in that version. + """ + if not hasattr(forms, 'FileField'): + return None + defaults = {'form_class': forms.FileField} + defaults.update(kwargs) + return super(BlobProperty, self).get_form_field(**defaults) + + def get_value_for_form(self, instance): + """Extract the property value from the instance for use in a form. + + There is no way to convert a Blob into an initial value for a file + upload, so we always return None. + """ + return None + + def make_value_from_form(self, value): + """Convert a form value to a property value. + + This extracts the content from the UploadedFile instance returned + by the FileField instance. + """ + if value.__class__.__name__ == 'UploadedFile': + return db.Blob(value.content) + return super(BlobProperty, self).make_value_from_form(value) + + +class DateTimeProperty(db.DateTimeProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a date-time property. + + This defaults to a DateTimeField instance, except if auto_now or + auto_now_add is set, in which case None is returned, as such + 'auto' fields should not be rendered as part of the form. + """ + if self.auto_now or self.auto_now_add: + return None + defaults = {'form_class': forms.DateTimeField} + defaults.update(kwargs) + return super(DateTimeProperty, self).get_form_field(**defaults) + + +class DateProperty(db.DateProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a date property. + + This defaults to a DateField instance, except if auto_now or + auto_now_add is set, in which case None is returned, as such + 'auto' fields should not be rendered as part of the form. + """ + if self.auto_now or self.auto_now_add: + return None + defaults = {'form_class': forms.DateField} + defaults.update(kwargs) + return super(DateProperty, self).get_form_field(**defaults) + + +class TimeProperty(db.TimeProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a time property. + + This defaults to a TimeField instance, except if auto_now or + auto_now_add is set, in which case None is returned, as such + 'auto' fields should not be rendered as part of the form. + """ + if self.auto_now or self.auto_now_add: + return None + defaults = {'form_class': forms.TimeField} + defaults.update(kwargs) + return super(TimeProperty, self).get_form_field(**defaults) + + +class IntegerProperty(db.IntegerProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for an integer property. + + This defaults to an IntegerField instance. + """ + defaults = {'form_class': forms.IntegerField} + defaults.update(kwargs) + return super(IntegerProperty, self).get_form_field(**defaults) + + +class FloatProperty(db.FloatProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for an integer property. + + This defaults to a FloatField instance when using Django 0.97 or + later. For 0.96 this defaults to the CharField class. + """ + defaults = {} + if hasattr(forms, 'FloatField'): + defaults['form_class'] = forms.FloatField + defaults.update(kwargs) + return super(FloatProperty, self).get_form_field(**defaults) + + +class BooleanProperty(db.BooleanProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a boolean property. + + This defaults to a BooleanField. + """ + defaults = {'form_class': forms.BooleanField} + defaults.update(kwargs) + return super(BooleanProperty, self).get_form_field(**defaults) + + def make_value_from_form(self, value): + """Convert a form value to a property value. + + This is needed to ensure that False is not replaced with None. + """ + if value is None: + return None + if isinstance(value, basestring) and value.lower() == 'false': + return False + return bool(value) + + +class StringListProperty(db.StringListProperty): + __metaclass__ = monkey_patch + + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a StringList property. + + This defaults to a Textarea widget with a blank initial value. + """ + defaults = {'widget': forms.Textarea, + 'initial': ''} + defaults.update(kwargs) + return super(StringListProperty, self).get_form_field(**defaults) + + def get_value_for_form(self, instance): + """Extract the property value from the instance for use in a form. + + This joins a list of strings with newlines. + """ + value = super(StringListProperty, self).get_value_for_form(instance) + if not value: + return None + if isinstance(value, list): + value = '\n'.join(value) + return value + + def make_value_from_form(self, value): + """Convert a form value to a property value. + + This breaks the string into lines. + """ + if not value: + return [] + if isinstance(value, basestring): + value = value.splitlines() + return value + + +class LinkProperty(db.LinkProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a URL property. + + This defaults to a URLField instance. + """ + defaults = {'form_class': forms.URLField} + defaults.update(kwargs) + return super(LinkProperty, self).get_form_field(**defaults) + + +class _WrapIter(object): + """Helper class whose iter() calls a given function to get an iterator.""" + + def __init__(self, function): + self._function = function + + def __iter__(self): + return self._function() + + +class ModelChoiceField(forms.Field): + + default_error_messages = { + 'invalid_choice': _(u'Please select a valid choice. ' + u'That choice is not one of the available choices.'), + } + + def __init__(self, reference_class, query=None, choices=None, + empty_label=u'---------', + required=True, widget=forms.Select, label=None, initial=None, + help_text=None, *args, **kwargs): + """Constructor. + + Args: + reference_class: required; the db.Model subclass used in the reference + query: optional db.Query; default db.Query(reference_class) + choices: optional explicit list of (value, label) pairs representing + available choices; defaults to dynamically iterating over the + query argument (or its default) + empty_label: label to be used for the default selection item in + the widget; this is prepended to the choices + required, widget, label, initial, help_text, *args, **kwargs: + like for forms.Field.__init__(); widget defaults to forms.Select + """ + assert issubclass(reference_class, db.Model) + if query is None: + query = db.Query(reference_class) + assert isinstance(query, db.Query) + super(ModelChoiceField, self).__init__(required, widget, label, initial, + help_text, *args, **kwargs) + self.empty_label = empty_label + self.reference_class = reference_class + self._query = query + self._choices = choices + self._update_widget_choices() + + def _update_widget_choices(self): + """Helper to copy the choices to the widget.""" + self.widget.choices = self.choices + + + def _get_query(self): + """Getter for the query attribute.""" + return self._query + + def _set_query(self, query): + """Setter for the query attribute. + + As a side effect, the widget's choices are updated. + """ + self._query = query + self._update_widget_choices() + + query = property(_get_query, _set_query) + + def _generate_choices(self): + """Generator yielding (key, label) pairs from the query results.""" + yield ('', self.empty_label) + for inst in self._query: + yield (inst.key(), unicode(inst)) + + + def _get_choices(self): + """Getter for the choices attribute. + + This is required to return an object that can be iterated over + multiple times. + """ + if self._choices is not None: + return self._choices + return _WrapIter(self._generate_choices) + + def _set_choices(self, choices): + """Setter for the choices attribute. + + As a side effect, the widget's choices are updated. + """ + self._choices = choices + self._update_widget_choices() + + choices = property(_get_choices, _set_choices) + + def clean(self, value): + """Override Field.clean() to do reference-specific value cleaning. + + This turns a non-empty value into a model instance. + """ + value = super(ModelChoiceField, self).clean(value) + if not value: + return None + instance = db.get(value) + if instance is None: + raise db.BadValueError(self.error_messages['invalid_choice']) + return instance + + +class ReferenceProperty(db.ReferenceProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a reference property. + + This defaults to a ModelChoiceField instance. + """ + defaults = {'form_class': ModelChoiceField, + 'reference_class': self.reference_class} + defaults.update(kwargs) + return super(ReferenceProperty, self).get_form_field(**defaults) + + def get_value_for_form(self, instance): + """Extract the property value from the instance for use in a form. + + This return the key object for the referenced object, or None. + """ + value = super(ReferenceProperty, self).get_value_for_form(instance) + if value is not None: + value = value.key() + return value + + def make_value_from_form(self, value): + """Convert a form value to a property value. + + This turns a key string or object into a model instance. + """ + if value: + if not isinstance(value, db.Model): + value = db.get(value) + return value + + +class _ReverseReferenceProperty(db._ReverseReferenceProperty): + __metaclass__ = monkey_patch + + def get_form_field(self, **kwargs): + """Return a Django form field appropriate for a reverse reference. + + This always returns None, since reverse references are always + automatic. + """ + return None + + +def property_clean(prop, value): + """Apply Property level validation to value. + + Calls .make_value_from_form() and .validate() on the property and catches + exceptions generated by either. The exceptions are converted to + forms.ValidationError exceptions. + + Args: + prop: The property to validate against. + value: The value to validate. + + Raises: + forms.ValidationError if the value cannot be validated. + """ + if value is not None: + try: + prop.validate(prop.make_value_from_form(value)) + except (db.BadValueError, ValueError), e: + raise forms.ValidationError(unicode(e)) + + +class ModelFormOptions(object): + """A simple class to hold internal options for a ModelForm class. + + Instance attributes: + model: a db.Model class, or None + fields: list of field names to be defined, or None + exclude: list of field names to be skipped, or None + + These instance attributes are copied from the 'Meta' class that is + usually present in a ModelForm class, and all default to None. + """ + + + def __init__(self, options=None): + self.model = getattr(options, 'model', None) + self.fields = getattr(options, 'fields', None) + self.exclude = getattr(options, 'exclude', None) + + +class ModelFormMetaclass(type): + """The metaclass for the ModelForm class defined below. + + This is our analog of Django's own ModelFormMetaclass. (We + can't conveniently subclass that class because there are quite a few + differences.) + + See the docs for ModelForm below for a usage example. + """ + + def __new__(cls, class_name, bases, attrs): + """Constructor for a new ModelForm class instance. + + The signature of this method is determined by Python internals. + + All Django Field instances are removed from attrs and added to + the base_fields attribute instead. Additional Field instances + are added to this based on the Datastore Model class specified + by the Meta attribute. + """ + fields = sorted(((field_name, attrs.pop(field_name)) + for field_name, obj in attrs.items() + if isinstance(obj, forms.Field)), + key=lambda obj: obj[1].creation_counter) + for base in bases[::-1]: + if hasattr(base, 'base_fields'): + fields = base.base_fields.items() + fields + declared_fields = django.utils.datastructures.SortedDict() + for field_name, obj in fields: + declared_fields[field_name] = obj + + opts = ModelFormOptions(attrs.get('Meta', None)) + attrs['_meta'] = opts + + base_models = [] + for base in bases: + base_opts = getattr(base, '_meta', None) + base_model = getattr(base_opts, 'model', None) + if base_model is not None: + base_models.append(base_model) + if len(base_models) > 1: + raise django.core.exceptions.ImproperlyConfigured( + "%s's base classes define more than one model." % class_name) + + if opts.model is not None: + if base_models and base_models[0] is not opts.model: + raise django.core.exceptions.ImproperlyConfigured( + '%s defines a different model than its parent.' % class_name) + + model_fields = django.utils.datastructures.SortedDict() + for name, prop in sorted(opts.model.properties().iteritems(), + key=lambda prop: prop[1].creation_counter): + if opts.fields and name not in opts.fields: + continue + if opts.exclude and name in opts.exclude: + continue + form_field = prop.get_form_field() + if form_field is not None: + model_fields[name] = form_field + + model_fields.update(declared_fields) + attrs['base_fields'] = model_fields + + props = opts.model.properties() + for name, field in model_fields.iteritems(): + prop = props.get(name) + if prop: + def clean_for_property_field(value, prop=prop, old_clean=field.clean): + value = old_clean(value) + property_clean(prop, value) + return value + field.clean = clean_for_property_field + else: + attrs['base_fields'] = declared_fields + + return super(ModelFormMetaclass, cls).__new__(cls, + class_name, bases, attrs) + + +class BaseModelForm(forms.BaseForm): + """Base class for ModelForm. + + This overrides the forms.BaseForm constructor and adds a save() method. + + This class does not have a special metaclass; the magic metaclass is + added by the subclass ModelForm. + """ + + def __init__(self, data=None, files=None, auto_id=None, prefix=None, + initial=None, error_class=None, label_suffix=None, + instance=None): + """Constructor. + + Args (all optional and defaulting to None): + data: dict of data values, typically from a POST request) + files: dict of file upload values; Django 0.97 or later only + auto_id, prefix: see Django documentation + initial: dict of initial values + error_class, label_suffix: see Django 0.97 or later documentation + instance: Model instance to be used for additional initial values + + Except for initial and instance, these arguments are passed on to + the forms.BaseForm constructor unchanged, but only if not None. + Some arguments (files, error_class, label_suffix) are only + supported by Django 0.97 or later. Leave these blank (i.e. None) + when using Django 0.96. Their default values will be used with + Django 0.97 or later even when they are explicitly set to None. + """ + opts = self._meta + self.instance = instance + object_data = {} + if instance is not None: + for name, prop in instance.properties().iteritems(): + if opts.fields and name not in opts.fields: + continue + if opts.exclude and name in opts.exclude: + continue + object_data[name] = prop.get_value_for_form(instance) + if initial is not None: + object_data.update(initial) + kwargs = dict(data=data, files=files, auto_id=auto_id, + prefix=prefix, initial=object_data, + error_class=error_class, label_suffix=label_suffix) + kwargs = dict((name, value) + for name, value in kwargs.iteritems() + if value is not None) + super(BaseModelForm, self).__init__(**kwargs) + + def save(self, commit=True): + """Save this form's cleaned data into a model instance. + + Args: + commit: optional bool, default True; if true, the model instance + is also saved to the datastore. + + Returns: + A model instance. If a model instance was already associated + with this form instance (either passed to the constructor with + instance=... or by a previous save() call), that same instance + is updated and returned; if no instance was associated yet, one + is created by this call. + + Raises: + ValueError if the data couldn't be validated. + """ + if not self.is_bound: + raise ValueError('Cannot save an unbound form') + opts = self._meta + instance = self.instance + if instance is None: + fail_message = 'created' + else: + fail_message = 'updated' + if self.errors: + raise ValueError("The %s could not be %s because the data didn't " + 'validate.' % (opts.model.kind(), fail_message)) + cleaned_data = self._cleaned_data() + converted_data = {} + propiter = itertools.chain( + opts.model.properties().iteritems(), + iter([('key_name', StringProperty(name='key_name'))]) + ) + for name, prop in propiter: + value = cleaned_data.get(name) + if value is not None: + converted_data[name] = prop.make_value_from_form(value) + try: + if instance is None: + instance = opts.model(**converted_data) + self.instance = instance + else: + for name, value in converted_data.iteritems(): + if name == 'key_name': + continue + setattr(instance, name, value) + except db.BadValueError, err: + raise ValueError('The %s could not be %s (%s)' % + (opts.model.kind(), fail_message, err)) + if commit: + instance.put() + return instance + + def _cleaned_data(self): + """Helper to retrieve the cleaned data attribute. + + In Django 0.96 this attribute was called self.clean_data. In 0.97 + and later it's been renamed to self.cleaned_data, to avoid a name + conflict. This helper abstracts the difference between the + versions away from its caller. + """ + try: + return self.cleaned_data + except AttributeError: + return self.clean_data + + +class ModelForm(BaseModelForm): + """A Django form tied to a Datastore model. + + Note that this particular class just sets the metaclass; all other + functionality is defined in the base class, BaseModelForm, above. + + Usage example: + + from google.appengine.ext import db + from google.appengine.ext.db import djangoforms + + # First, define a model class + class MyModel(db.Model): + foo = db.StringProperty() + bar = db.IntegerProperty(required=True, default=42) + + # Now define a form class + class MyForm(djangoforms.ModelForm): + class Meta: + model = MyModel + + You can now instantiate MyForm without arguments to create an + unbound form, or with data from a POST request to create a bound + form. You can also pass a model instance with the instance=... + keyword argument to create an unbound (!) form whose initial values + are taken from the instance. For bound forms, use the save() method + to return a model instance. + + Like Django's own corresponding ModelForm class, the nested Meta + class can have two other attributes: + + fields: if present and non-empty, a list of field names to be + included in the form; properties not listed here are + excluded from the form + + exclude: if present and non-empty, a list of field names to be + excluded from the form + + If exclude and fields are both non-empty, names occurring in both + are excluded (i.e. exclude wins). By default all property in the + model have a corresponding form field defined. + + It is also possible to define form fields explicitly. This gives + more control over the widget used, constraints, initial value, and + so on. Such form fields are not affected by the nested Meta class's + fields and exclude attributes. + + If you define a form field named 'key_name' it will be treated + specially and will be used as the value for the key_name parameter + to the Model constructor. This allows you to create instances with + named keys. The 'key_name' field will be ignored when updating an + instance (although it will still be shown on the form). + """ + + __metaclass__ = ModelFormMetaclass diff --git a/google_appengine/google/appengine/ext/db/polymodel.py b/google_appengine/google/appengine/ext/db/polymodel.py new file mode 100755 index 0000000..805a2c5 --- /dev/null +++ b/google_appengine/google/appengine/ext/db/polymodel.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Support for polymorphic models and queries. + +The Model class on its own is only able to support functional polymorphism. +It is possible to create a subclass of Model and then subclass that one as +many generations as necessary and those classes will share all the same +properties and behaviors. The problem is that subclassing Model in this way +places each subclass in their own Kind. This means that it is not possible +to do polymorphic queries. Building a query on a base class will only return +instances of that class from the Datastore, while queries on a subclass will +only return those instances. + +This module allows applications to specify class hierarchies that support +polymorphic queries. +""" + + +from google.appengine.ext import db + +_class_map = {} + +_CLASS_KEY_PROPERTY = 'class' + + +class _ClassKeyProperty(db.ListProperty): + """Property representing class-key property of a polymorphic class. + + The class key is a list of strings describing an polymorphic instances + place within its class hierarchy. This property is automatically calculated. + For example: + + class Foo(PolyModel): ... + class Bar(Foo): ... + class Baz(Bar): ... + + Foo.class_key() == ['Foo'] + Bar.class_key() == ['Foo', 'Bar'] + Baz.class_key() == ['Foo', 'Bar', 'Baz'] + """ + + def __init__(self, name): + super(_ClassKeyProperty, self).__init__(name=name, + item_type=str, + default=None) + + def __set__(self, *args): + raise db.DerivedPropertyError( + 'Class-key is a derived property and cannot be set.') + + def __get__(self, model_instance, model_class): + if model_instance is None: + return self + return [cls.__name__ for cls in model_class.__class_hierarchy__] + + +class PolymorphicClass(db.PropertiedClass): + """Meta-class for initializing PolymorphicClasses. + + This class extends PropertiedClass to add a few static attributes to + new polymorphic classes necessary for their correct functioning. + + """ + + def __init__(cls, name, bases, dct): + """Initializes a class that belongs to a polymorphic hierarchy. + + This method configures a few built-in attributes of polymorphic + models: + + __root_class__: If the new class is a root class, __root_class__ is set to + itself so that it subclasses can quickly know what the root of + their hierarchy is and what kind they are stored in. + __class_hierarchy__: List of classes describing the new model's place + in the class hierarchy in reverse MRO order. The first element is + always the root class while the last element is always the new class. + + MRO documentation: http://www.python.org/download/releases/2.3/mro/ + + For example: + class Foo(PolymorphicClass): ... + + class Bar(Foo): ... + + class Baz(Bar): ... + + Foo.__class_hierarchy__ == [Foo] + Bar.__class_hierarchy__ == [Foo, Bar] + Baz.__class_hierarchy__ == [Foo, Bar, Baz] + + Unless the class is a root class or PolyModel itself, it is not + inserted in to the kind-map like other models. However, all polymorphic + classes, are inserted in to the class-map which maps the class-key to + implementation. This class key is consulted using the polymorphic instances + discriminator (the 'class' property of the entity) when loading from the + datastore. + """ + if name == 'PolyModel': + super(PolymorphicClass, cls).__init__(name, bases, dct, map_kind=False) + return + + elif PolyModel in bases: + if getattr(cls, '__class_hierarchy__', None): + raise db.ConfigurationError(('%s cannot derive from PolyModel as ' + '__class_hierarchy__ is already defined.') % cls.__name__) + cls.__class_hierarchy__ = [cls] + cls.__root_class__ = cls + super(PolymorphicClass, cls).__init__(name, bases, dct) + else: + super(PolymorphicClass, cls).__init__(name, bases, dct, map_kind=False) + + cls.__class_hierarchy__ = [c for c in reversed(cls.mro()) + if issubclass(c, PolyModel) and c != PolyModel] + + if cls.__class_hierarchy__[0] != cls.__root_class__: + raise db.ConfigurationError( + '%s cannot be derived from both root classes %s and %s' % + (cls.__name__, + cls.__class_hierarchy__[0].__name__, + cls.__root_class__.__name__)) + + _class_map[cls.class_key()] = cls + + +class PolyModel(db.Model): + """Base-class for models that supports polymorphic queries. + + Use this class to build hierarchies that can be queried based + on their types. + + Example: + + consider the following model hierarchy: + + +------+ + |Animal| + +------+ + | + +-----------------+ + | | + +------+ +------+ + |Canine| |Feline| + +------+ +------+ + | | + +-------+ +-------+ + | | | | + +---+ +----+ +---+ +-------+ + |Dog| |Wolf| |Cat| |Panther| + +---+ +----+ +---+ +-------+ + + This class hierarchy has three levels. The first is the "root class". + All models in a single class hierarchy must inherit from this root. All + models in the hierarchy are stored as the same kind as the root class. + For example, Panther entities when stored to the datastore are of the kind + 'Animal'. Querying against the Animal kind will retrieve Cats, Dogs and + Canines, for example, that match your query. Different classes stored + in the root class' kind are identified by their class-key. When loaded + from the datastore, it is mapped to the appropriate implementation class. + + Polymorphic properties: + + Properties that are defined in a given base-class within a hierarchy are + stored in the datastore for all sub-casses only. So, if the Feline class + had a property called 'whiskers', the Cat and Panther enties would also + have whiskers, but not Animal, Canine, Dog or Wolf. + + Polymorphic queries: + + When written to the datastore, all polymorphic objects automatically have + a property called 'class' that you can query against. Using this property + it is possible to easily write a GQL query against any sub-hierarchy. For + example, to fetch only Canine objects, including all Dogs and Wolves: + + db.GqlQuery("SELECT * FROM Animal WHERE class='Canine'") + + And alternate method is to use the 'all' or 'gql' methods of the Canine + class: + + Canine.all() + Canine.gql('') + + The 'class' property is not meant to be used by your code other than + for queries. Since it is supposed to represents the real Python class + it is intended to be hidden from view. + + Root class: + + The root class is the class from which all other classes of the hierarchy + inherits from. Each hierarchy has a single root class. A class is a + root class if it is an immediate child of PolyModel. The subclasses of + the root class are all the same kind as the root class. In other words: + + Animal.kind() == Feline.kind() == Panther.kind() == 'Animal' + """ + + __metaclass__ = PolymorphicClass + + _class = _ClassKeyProperty(name=_CLASS_KEY_PROPERTY) + + def __new__(cls, *args, **kwds): + """Prevents direct instantiation of PolyModel.""" + if cls is PolyModel: + raise NotImplementedError() + return super(PolyModel, cls).__new__(cls, *args, **kwds) + + @classmethod + def kind(cls): + """Get kind of polymorphic model. + + Overridden so that all subclasses of root classes are the same kind + as the root. + + Returns: + Kind of entity to write to datastore. + """ + if cls is cls.__root_class__: + return super(PolyModel, cls).kind() + else: + return cls.__root_class__.kind() + + @classmethod + def class_key(cls): + """Caclulate the class-key for this class. + + Returns: + Class key for class. By default this is a the list of classes + of the hierarchy, starting with the root class and walking its way + down to cls. + """ + if not hasattr(cls, '__class_hierarchy__'): + raise NotImplementedError( + 'Cannot determine class key without class hierarchy') + return tuple(cls.class_name() for cls in cls.__class_hierarchy__) + + @classmethod + def class_name(cls): + """Calculate class name for this class. + + Returns name to use for each classes element within its class-key. Used + to discriminate between different classes within a class hierarchy's + Datastore kind. + + The presence of this method allows developers to use a different class + name in the datastore from what is used in Python code. This is useful, + for example, for renaming classes without having to migrate instances + already written to the datastore. For example, to rename a polymorphic + class Contact to SimpleContact, you could convert: + + # Class key is ['Information'] + class Information(PolyModel): ... + + # Class key is ['Information', 'Contact'] + class Contact(Information): ... + + to: + + # Class key is still ['Information', 'Contact'] + class SimpleContact(Information): + ... + @classmethod + def class_name(cls): + return 'Contact' + + # Class key is ['Information', 'Contact', 'ExtendedContact'] + class ExtendedContact(SimpleContact): ... + + This would ensure that all objects written previously using the old class + name would still be loaded. + + Returns: + Name of this class. + """ + return cls.__name__ + + @classmethod + def from_entity(cls, entity): + """Load from entity to class based on discriminator. + + Rather than instantiating a new Model instance based on the kind + mapping, this creates an instance of the correct model class based + on the entities class-key. + + Args: + entity: Entity loaded directly from datastore. + + Raises: + KindError when there is no class mapping based on discriminator. + """ + if (_CLASS_KEY_PROPERTY in entity and + tuple(entity[_CLASS_KEY_PROPERTY]) != cls.class_key()): + key = tuple(entity[_CLASS_KEY_PROPERTY]) + try: + poly_class = _class_map[key] + except KeyError: + raise db.KindError('No implementation for class \'%s\'' % key) + return poly_class.from_entity(entity) + return super(PolyModel, cls).from_entity(entity) + + @classmethod + def all(cls, **kwds): + """Get all instance of a class hierarchy. + + Args: + kwds: Keyword parameters passed on to Model.all. + + Returns: + Query with filter set to match this class' discriminator. + """ + query = super(PolyModel, cls).all(**kwds) + if cls != cls.__root_class__: + query.filter(_CLASS_KEY_PROPERTY + ' =', cls.class_name()) + return query + + @classmethod + def gql(cls, query_string, *args, **kwds): + """Returns a polymorphic query using GQL query string. + + This query is polymorphic in that it has its filters configured in a way + to retrieve instances of the model or an instance of a subclass of the + model. + + Args: + query_string: properly formatted GQL query string with the + 'SELECT * FROM ' part omitted + *args: rest of the positional arguments used to bind numeric references + in the query. + **kwds: dictionary-based arguments (for named parameters). + """ + if cls == cls.__root_class__: + return super(PolyModel, cls).gql(query_string, *args, **kwds) + else: + from google.appengine.ext import gql + + query = db.GqlQuery('SELECT * FROM %s %s' % (cls.kind(), query_string)) + + query_filter = [('nop', + [gql.Literal(cls.class_name())])] + query._proto_query.filters()[('class', '=')] = query_filter + query.bind(*args, **kwds) + return query diff --git a/google_appengine/google/appengine/ext/db/polymodel.pyc b/google_appengine/google/appengine/ext/db/polymodel.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92f82721415352ed387e942dd014eaf454162370 GIT binary patch literal 13705 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/db/stats.py b/google_appengine/google/appengine/ext/db/stats.py new file mode 100755 index 0000000..b309d6a --- /dev/null +++ b/google_appengine/google/appengine/ext/db/stats.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Models to be used when accessing app specific datastore usage statistics. + +These entities cannot be created by users, but are populated in the +application's datastore by offline processes run by the Google App Engine team. +""" + + + + +from google.appengine.ext import db + + +class BaseStatistic(db.Model): + """Base Statistic Model class. + + The 'bytes' attribute represents the total number of bytes taken up in the + datastore for the statistic instance. The 'count' attribute is the + total number of occurrences of the statistic in the datastore. The + 'timestamp' is when the statistic instance was written to the datastore. + """ + STORED_KIND_NAME = '__BaseStatistic__' + + bytes = db.IntegerProperty() + + count = db.IntegerProperty() + + timestamp = db.DateTimeProperty() + + @classmethod + def kind(cls): + """Kind name override.""" + return cls.STORED_KIND_NAME + + +class BaseKindStatistic(BaseStatistic): + """Base Statistic Model class for stats associated with kinds. + + The 'kind_name' attribute represents the name of the kind associated with the + statistic instance. + """ + STORED_KIND_NAME = '__BaseKindStatistic__' + + kind_name = db.StringProperty() + + +class GlobalStat(BaseStatistic): + """An aggregate of all entities across the entire application. + + This statistic only has a single instance in the datastore that contains the + total number of entities stored and the total number of bytes they take up. + """ + STORED_KIND_NAME = '__Stat_Total__' + + +class KindStat(BaseKindStatistic): + """An aggregate of all entities at the granularity of their Kind. + + There is an instance of the KindStat for every Kind that is in the + application's datastore. This stat contains per-Kind statistics. + """ + STORED_KIND_NAME = '__Stat_Kind__' + + +class KindRootEntityStat(BaseKindStatistic): + """Statistics of the number of root entities in the datastore by Kind. + + There is an instance of the KindRootEntityState for every Kind that is in the + application's datastore and has an instance that is a root entity. This stat + contains statistics regarding these root entity instances. + """ + STORED_KIND_NAME = '__Stat_Kind_IsRootEntity__' + + +class KindNonRootEntityStat(BaseKindStatistic): + """Statistics of the number of non root entities in the datastore by Kind. + + There is an instance of the KindNonRootEntityStat for every Kind that is in + the application's datastore that is a not a root entity. This stat contains + statistics regarding thse non root entity instances. + """ + STORED_KIND_NAME = '__Stat_Kind_NotRootEntity__' + + +class PropertyTypeStat(BaseStatistic): + """An aggregate of all properties across the entire application by type. + + There is an instance of the PropertyTypeStat for every property type + (google.appengine.api.datastore_types._PROPERTY_TYPES) in use by the + application in its datastore. + """ + STORED_KIND_NAME = '__Stat_PropertyType__' + + property_type = db.StringProperty() + + +class KindPropertyTypeStat(BaseKindStatistic): + """Statistics on (kind, property_type) tuples in the app's datastore. + + There is an instance of the KindPropertyTypeStat for every + (kind, property_type) tuple in the application's datastore. + """ + STORED_KIND_NAME = '__Stat_PropertyType_Kind__' + + property_type = db.StringProperty() + + +class KindPropertyNameStat(BaseKindStatistic): + """Statistics on (kind, property_name) tuples in the app's datastore. + + There is an instance of the KindPropertyNameStat for every + (kind, property_name) tuple in the application's datastore. + """ + STORED_KIND_NAME = '__Stat_PropertyName_Kind__' + + property_name = db.StringProperty() + + +class KindPropertyNamePropertyTypeStat(BaseKindStatistic): + """Statistic on (kind, property_name, property_type) tuples in the datastore. + + There is an instance of the KindPropertyNamePropertyTypeStat for every + (kind, property_name, property_type) tuple in the application's datastore. + """ + STORED_KIND_NAME = '__Stat_PropertyType_PropertyName_Kind__' + + property_type = db.StringProperty() + + property_name = db.StringProperty() diff --git a/google_appengine/google/appengine/ext/db/stats.pyc b/google_appengine/google/appengine/ext/db/stats.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58d1cbe7e80417f9b4928a86fe60b9b7076aa050 GIT binary patch literal 6082 zcwWU=OLH4V5FW{vY|D>4?GVCa3aGMD6{HJC3P@sCNQF2?)+Oe!HQF6ZTUpI4J0py$ za)T;<2|t80C+<{nfM39c;so8jZ&|VuC#p!L(QfZdcTay`_sr~Xe{VMae8>CBWyxn5 zeqX@P{0oi8*fF#f=2h9V3Ja?2xXLQt5|dTtFR>GNu*`mB><7l49xpSm#^f?d>zTC1 zyp>eCnn~--TVt|D(ncm-VcvQw-N>Y?%-c+*TbXo?d6!bXmosUDc~{cDS2O84^RA`R z>zQX< z*v(2YY&HD<;M1(YL~RuT@7YcGyC;l1N?p&WfgSE)TpmCpff^MeNEHckyo6x*H4Gx4 zn_m<7{wiGqv$WcZfUD84CnJalr~cfEXMor!Ok>CkaXZlwjm&_8PH?SU*-C7>dZcX8 zrf(80lh(BBj-p7yScA8dYDth;j@5&fUF#1e^c#k|sG8U{-6MV~;CbX*i(T}DK6K~` z1k-U!=sAvy=%&B>815Blo-^h%AUX=oHE16B%Hv85CD-4{DDpkoig|GvnxpQM&iva!^CeXb<#Iwv`(R-QLruD$v3+?LHCE0sczjUm|Gd0{Ojd zd%ZT!+qA;5#Y*%HoNA$BD(6+g$@S# zJyeQQudmcA_38$F32cNKfjgV5IF6E)vw6u1S%~2^KeO|)kWz)*;&7*3pUTpyZwDMv zDr70%5s-_<#i$<%1#3T) z`*D&ee#FsenrIO%MMC8mCHa(kMj0!in2$*mBL+jVWpKu3qE+BUjxeOl$E2pXrvZ>Q zbCB@y8^Q=H!j~nCt|l0Dw6^;g+wn!v`lx~xBb{&>;heb$FS{R)vk7@As1PvaVd_Hf86E{HD*BK}3 zz=`dI69-zo8ZF+RLkp$V>!ijRe`k{eu^7mi9mvbeyN-^xhgU^mlp4m(c{_dDIEPWS2I{?TouEF4FZ6J)vI;4|NviQ~t1 zj(E|#dE2rM?qq2lNgXGT7RiHKF`mh0tWKLT%y=Z~>tw?(tXwA)Z{lY24lm90Hn*cN z$cqH{U0RRlQDK_bIUSGyvyB#PPTdPk)bAXh=3fe)^o`Dt&sxrhzvbw-o(E;<*_@&loJE(kzW_Nk% S=FZOg&Ki6h@ZEsx_5T6Z_?tEW literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/deferred/__init__.py b/google_appengine/google/appengine/ext/deferred/__init__.py new file mode 100755 index 0000000..63b922e --- /dev/null +++ b/google_appengine/google/appengine/ext/deferred/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + + + + +from deferred import * + + +if __name__ == "__main__": + main() diff --git a/google_appengine/google/appengine/ext/deferred/deferred.py b/google_appengine/google/appengine/ext/deferred/deferred.py new file mode 100755 index 0000000..7672f32 --- /dev/null +++ b/google_appengine/google/appengine/ext/deferred/deferred.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A module that handles deferred execution of callables via the task queue. + +Tasks consist of a callable and arguments to pass to it. The callable and its +arguments are serialized and put on the task queue, which deserializes and +executes them. The following callables can be used as tasks: + +1) Functions defined in the top level of a module +2) Classes defined in the top level of a module +3) Instances of classes in (2) that implement __call__ +4) Instance methods of objects of classes in (2) +5) Class methods of classes in (2) +6) Built-in functions +7) Built-in methods + +The following callables can NOT be used as tasks: +1) Nested functions or closures +2) Nested classes or objects of them +3) Lambda functions +4) Static methods + +The arguments to the callable, and the object (in the case of method or object +calls) must all be pickleable. + +If you want your tasks to execute reliably, don't use mutable global variables; +they are not serialized with the task and may not be the same when your task +executes as they were when it was enqueued (in fact, they will almost certainly +be different). + +If your app relies on manipulating the import path, make sure that the function +you are deferring is defined in a module that can be found without import path +manipulation. Alternately, you can include deferred.TaskHandler in your own +webapp application instead of using the easy-install method detailed below. + +When you create a deferred task using deferred.defer, the task is serialized, +and an attempt is made to add it directly to the task queue. If the task is too +big (larger than about 10 kilobytes when serialized), a datastore entry will be +created for the task, and a new task will be enqueued, which will fetch the +original task from the datastore and execute it. This is much less efficient +than the direct execution model, so it's a good idea to minimize the size of +your tasks when possible. + +In order for tasks to be processed, you need to set up the handler. Add the +following to your app.yaml handlers section: + +handlers: +- url: /_ah/queue/deferred + script: $PYTHON_LIB/google/appengine/ext/deferred/handler.py + login: admin + +By default, the deferred module uses the URL above, and the default queue. + +Example usage: + + def do_something_later(key, amount): + entity = MyModel.get(key) + entity.total += amount + entity.put() + + # Use default URL and queue name, no task name, execute ASAP. + deferred.defer(do_something_later, 20) + + # Providing non-default task queue arguments + deferred.defer(do_something_later, 20, _queue="foo", countdown=60) +""" + + + + + +import logging +import os +import pickle +import types + +from google.appengine.api.labs import taskqueue +from google.appengine.ext import db +from google.appengine.ext import webapp +from google.appengine.ext.webapp.util import run_wsgi_app + + +_TASKQUEUE_HEADERS = {"Content-Type": "application/octet-stream"} +_DEFAULT_URL = "/_ah/queue/deferred" +_DEFAULT_QUEUE = "default" + + +class Error(Exception): + """Base class for exceptions in this module.""" + + +class PermanentTaskFailure(Error): + """Indicates that a task failed, and will never succeed.""" + + +def run(data): + """Unpickles and executes a task. + + Args: + data: A pickled tuple of (function, args, kwargs) to execute. + Returns: + The return value of the function invocation. + """ + try: + func, args, kwds = pickle.loads(data) + except Exception, e: + raise PermanentTaskFailure(e) + else: + return func(*args, **kwds) + + +class _DeferredTaskEntity(db.Model): + """Datastore representation of a deferred task. + + This is used in cases when the deferred task is too big to be included as + payload with the task queue entry. + """ + data = db.BlobProperty(required=True) + + +def run_from_datastore(key): + """Retrieves a task from the datastore and executes it. + + Args: + key: The datastore key of a _DeferredTaskEntity storing the task. + Returns: + The return value of the function invocation. + """ + entity = _DeferredTaskEntity.get(key) + if not entity: + raise PermanentTaskFailure() + try: + ret = run(entity.data) + entity.delete() + except PermanentTaskFailure: + entity.delete() + raise + + +def invoke_member(obj, membername, *args, **kwargs): + """Retrieves a member of an object, then calls it with the provided arguments. + + Args: + obj: The object to operate on. + membername: The name of the member to retrieve from ojb. + args: Positional arguments to pass to the method. + kwargs: Keyword arguments to pass to the method. + Returns: + The return value of the method invocation. + """ + return getattr(obj, membername)(*args, **kwargs) + + +def _curry_callable(obj, *args, **kwargs): + """Takes a callable and arguments and returns a task queue tuple. + + The returned tuple consists of (callable, args, kwargs), and can be pickled + and unpickled safely. + + Args: + obj: The callable to curry. See the module docstring for restrictions. + args: Positional arguments to call the callable with. + kwargs: Keyword arguments to call the callable with. + Returns: + A tuple consisting of (callable, args, kwargs) that can be evaluated by + run() with equivalent effect of executing the function directly. + Raises: + ValueError: If the passed in object is not of a valid callable type. + """ + if isinstance(obj, types.MethodType): + return (invoke_member, (obj.im_self, obj.im_func.__name__) + args, kwargs) + elif isinstance(obj, types.BuiltinMethodType): + if not obj.__self__: + return (obj, args, kwargs) + else: + return (invoke_member, (obj.__self__, obj.__name__) + args, kwargs) + elif isinstance(obj, types.ObjectType) and hasattr(obj, "__call__"): + return (obj, args, kwargs) + elif isinstance(obj, (types.FunctionType, types.BuiltinFunctionType, + types.ClassType, types.UnboundMethodType)): + return (obj, args, kwargs) + else: + raise ValueError("obj must be callable") + + +def serialize(obj, *args, **kwargs): + """Serializes a callable into a format recognized by the deferred executor. + + Args: + obj: The callable to serialize. See module docstring for restrictions. + args: Positional arguments to call the callable with. + kwargs: Keyword arguments to call the callable with. + Returns: + A serialized representation of the callable. + """ + curried = _curry_callable(obj, *args, **kwargs) + return pickle.dumps(curried, protocol=pickle.HIGHEST_PROTOCOL) + + +def defer(obj, *args, **kwargs): + """Defers a callable for execution later. + + The default deferred URL of /_ah/queue/deferred will be used unless an + alternate URL is explicitly specified. If you want to use the default URL for + a queue, specify _url=None. If you specify a different URL, you will need to + install the handler on that URL (see the module docstring for details). + + Args: + obj: The callable to execute. See module docstring for restrictions. + _countdown, _eta, _name, _transactional, _url, _queue: Passed through to + the task queue - see the task queue documentation for details. + args: Positional arguments to call the callable with. + kwargs: Any other keyword arguments are passed through to the callable. + Returns: + A taskqueue.Task object which represents an enqueued callable. + """ + taskargs = dict((x, kwargs.pop(("_%s" % x), None)) + for x in ("countdown", "eta", "name")) + taskargs["url"] = kwargs.pop("_url", _DEFAULT_URL) + transactional = kwargs.pop("_transactional", False) + taskargs["headers"] = _TASKQUEUE_HEADERS + queue = kwargs.pop("_queue", _DEFAULT_QUEUE) + pickled = serialize(obj, *args, **kwargs) + try: + task = taskqueue.Task(payload=pickled, **taskargs) + return task.add(queue, transactional=transactional) + except taskqueue.TaskTooLargeError: + key = _DeferredTaskEntity(data=pickled).put() + pickled = serialize(run_from_datastore, str(key)) + task = taskqueue.Task(payload=pickled, **taskargs) + return task.add(queue) + + +class TaskHandler(webapp.RequestHandler): + """A webapp handler class that processes deferred invocations.""" + + def post(self): + headers = ["%s:%s" % (k, v) for k, v in self.request.headers.items() + if k.lower().startswith("x-appengine-")] + logging.info(", ".join(headers)) + + try: + run(self.request.body) + except PermanentTaskFailure, e: + logging.exception("Permanent failure attempting to execute task") + + +application = webapp.WSGIApplication([(".*", TaskHandler)]) + + +def main(): + if os.environ["SERVER_SOFTWARE"].startswith("Devel"): + logging.warn("You are using deferred in a deprecated fashion. Please change" + " the request handler path for /_ah/queue/deferred in app.yaml" + " to $PYTHON_LIB/google/appengine/ext/deferred/handler.py to" + " avoid encountering import errors.") + run_wsgi_app(application) + + +if __name__ == "__main__": + main() diff --git a/google_appengine/google/appengine/ext/deferred/handler.py b/google_appengine/google/appengine/ext/deferred/handler.py new file mode 100755 index 0000000..20f2ba1 --- /dev/null +++ b/google_appengine/google/appengine/ext/deferred/handler.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Request handler module for the deferred library. + +See deferred.py for full documentation. +""" + + + + + +from google.appengine.ext import deferred +from google.appengine.ext.webapp.util import run_wsgi_app + + +def main(): + run_wsgi_app(deferred.application) + + +if __name__ == "__main__": + main() diff --git a/google_appengine/google/appengine/ext/ereporter/__init__.py b/google_appengine/google/appengine/ext/ereporter/__init__.py new file mode 100755 index 0000000..3ae417b --- /dev/null +++ b/google_appengine/google/appengine/ext/ereporter/__init__.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from ereporter import * diff --git a/google_appengine/google/appengine/ext/ereporter/ereporter.py b/google_appengine/google/appengine/ext/ereporter/ereporter.py new file mode 100755 index 0000000..989718c --- /dev/null +++ b/google_appengine/google/appengine/ext/ereporter/ereporter.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A logging handler that records information about unique exceptions. + +'Unique' in this case is defined as a given (exception class, location) tuple. +Unique exceptions are logged to the datastore with an example stacktrace and an +approximate count of occurrences, grouped by day and application version. + +A cron handler, in google.appengine.ext.ereporter.report_generator, constructs +and emails a report based on the previous day's exceptions. + +Example usage: + +In your handler script(s), add: + + import logging + from google.appengine.ext import ereporter + + ereporter.register_logger() + +In your app.yaml, add: + + handlers: + - url: /_ereporter/.* + script: $PYTHON_LIB/google/appengine/ext/ereporter/report_generator.py + login: admin + +In your cron.yaml, add: + + cron: + - description: Daily exception report + url: /_ereporter?sender=you@yourdomain.com + schedule: every day 00:00 + +This will cause a daily exception report to be generated and emailed to all +admins, with exception traces grouped by minor version. If you only want to +get exception information for the most recent minor version, add the +'versions=latest' argument to the query string. For other valid query string +arguments, see report_generator.py. + +If you anticipate a lot of exception traces (for example, if you're deploying +many minor versions, each of which may have its own set of exceptions), you +can ensure that the traces from the newest minor versions get included by adding +this to your index.yaml: + + indexes: + - kind: __google_ExceptionRecord + properties: + - name: date + - name: major_version + - name: minor_version + direction: desc +""" + + + + + +import datetime +import logging +import os +import sha +import traceback +import urllib + +from google.appengine.api import memcache +from google.appengine.ext import db +from google.appengine.ext import webapp + + +MAX_SIGNATURE_LENGTH = 256 + + +class ExceptionRecord(db.Model): + """Datastore model for a record of a unique exception.""" + + signature = db.StringProperty(required=True) + major_version = db.StringProperty(required=True) + minor_version = db.IntegerProperty(required=True) + date = db.DateProperty(required=True) + count = db.IntegerProperty(required=True, default=0) + + stacktrace = db.TextProperty(required=True) + http_method = db.TextProperty(required=True) + url = db.TextProperty(required=True) + handler = db.TextProperty(required=True) + + @classmethod + def get_key_name(cls, signature, version, date=None): + """Generates a key name for an exception record. + + Args: + signature: A signature representing the exception and its site. + version: The major/minor version of the app the exception occurred in. + date: The date the exception occurred. + + Returns: + The unique key name for this exception record. + """ + if not date: + date = datetime.date.today() + return '%s@%s:%s' % (signature, date, version) + + +class ExceptionRecordingHandler(logging.Handler): + """A handler that records exception data to the App Engine datastore.""" + + def __init__(self, log_interval=10): + """Constructs a new ExceptionRecordingHandler. + + Args: + log_interval: The minimum interval at which we will log an individual + exception. This is a per-exception timeout, so doesn't affect the + aggregate rate of exception logging, only the rate at which we record + ocurrences of a single exception, to prevent datastore contention. + """ + self.log_interval = log_interval + logging.Handler.__init__(self) + + @classmethod + def __RelativePath(cls, path): + """Rewrites a path to be relative to the app's root directory. + + Args: + path: The path to rewrite. + + Returns: + The path with the prefix removed, if that prefix matches the app's + root directory. + """ + cwd = os.getcwd() + if path.startswith(cwd): + path = path[len(cwd)+1:] + return path + + @classmethod + def __GetSignature(cls, exc_info): + """Returns a unique signature string for an exception. + + Args: + exc_info: The exc_info object for an exception. + + Returns: + A unique signature string for the exception, consisting of fully + qualified exception name and call site. + """ + ex_type, unused_value, trace = exc_info + frames = traceback.extract_tb(trace) + + fulltype = '%s.%s' % (ex_type.__module__, ex_type.__name__) + path, line_no = frames[-1][:2] + path = cls.__RelativePath(path) + site = '%s:%d' % (path, line_no) + signature = '%s@%s' % (fulltype, site) + if len(signature) > MAX_SIGNATURE_LENGTH: + signature = 'hash:%s' % sha.new(signature).hexdigest() + + return signature + + @classmethod + def __GetURL(cls): + """Returns the URL of the page currently being served. + + Returns: + The full URL of the page currently being served. + """ + if os.environ['SERVER_PORT'] == '80': + scheme = 'http://' + else: + scheme = 'https://' + host = os.environ['SERVER_NAME'] + script_name = urllib.quote(os.environ['SCRIPT_NAME']) + path_info = urllib.quote(os.environ['PATH_INFO']) + qs = os.environ.get('QUERY_STRING', '') + if qs: + qs = '?' + qs + return scheme + host + script_name + path_info + qs + + def __GetFormatter(self): + """Returns the log formatter for this handler. + + Returns: + The log formatter to use. + """ + if self.formatter: + return self.formatter + else: + return logging._defaultFormatter + + def emit(self, record): + """Log an error to the datastore, if applicable. + + Args: + The logging.LogRecord object. + See http://docs.python.org/library/logging.html#logging.LogRecord + """ + try: + if not record.exc_info: + return + + signature = self.__GetSignature(record.exc_info) + + if not memcache.add(signature, None, self.log_interval): + return + + db.run_in_transaction_custom_retries(1, self.__EmitTx, signature, + record.exc_info) + except Exception: + self.handleError(record) + + def __EmitTx(self, signature, exc_info): + """Run in a transaction to insert or update the record for this transaction. + + Args: + signature: The signature for this exception. + exc_info: The exception info record. + """ + today = datetime.date.today() + version = os.environ['CURRENT_VERSION_ID'] + major_ver, minor_ver = version.rsplit('.', 1) + minor_ver = int(minor_ver) + key_name = ExceptionRecord.get_key_name(signature, version) + + exrecord = ExceptionRecord.get_by_key_name(key_name) + if not exrecord: + exrecord = ExceptionRecord( + key_name=key_name, + signature=signature, + major_version=major_ver, + minor_version=minor_ver, + date=today, + stacktrace=self.__GetFormatter().formatException(exc_info), + http_method=os.environ['REQUEST_METHOD'], + url=self.__GetURL(), + handler=self.__RelativePath(os.environ['PATH_TRANSLATED'])) + + exrecord.count += 1 + exrecord.put() + + +def register_logger(logger=None): + if not logger: + logger = logging.getLogger() + handler = ExceptionRecordingHandler() + logger.addHandler(handler) + return handler diff --git a/google_appengine/google/appengine/ext/ereporter/report_generator.py b/google_appengine/google/appengine/ext/ereporter/report_generator.py new file mode 100755 index 0000000..f173f47 --- /dev/null +++ b/google_appengine/google/appengine/ext/ereporter/report_generator.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Generates and emails daily exception reports. + +See google/appengine/ext/ereporter/__init__.py for usage details. + +Valid query string arguments to the report_generator script include: +delete: Set to 'false' to prevent deletion of exception records from the + datastore after sending a report. Defaults to 'true'. +debug: Set to 'true' to return the report in the response instead of + emailing it. +date: The date to generate the report for, in yyyy-mm-dd format. Defaults to + yesterday's date. Useful for debugging. +max_results: Maximum number of entries to include in a report. +sender: The email address to use as the sender. Must be an administrator. +to: If specified, send reports to this address. If not specified, all + admins are sent the report. +versions: 'all' to report on all minor versions, or 'latest' for the latest. +""" + + + + + +import datetime +import itertools +import os +import re +from xml.sax import saxutils + +from google.appengine.api import mail +from google.appengine.ext import db +from google.appengine.ext import ereporter +from google.appengine.ext import webapp +from google.appengine.ext.webapp import template +from google.appengine.ext.webapp.util import run_wsgi_app + + +def isTrue(val): + """Determines if a textual value represents 'true'. + + Args: + val: A string, which may be 'true', 'yes', 't', '1' to indicate True. + Returns: + True or False + """ + val = val.lower() + return val == 'true' or val == 't' or val == '1' or val == 'yes' + + +class ReportGenerator(webapp.RequestHandler): + """Handler class to generate and email an exception report.""" + + DEFAULT_MAX_RESULTS = 100 + + def __init__(self, send_mail=mail.send_mail, + mail_admins=mail.send_mail_to_admins): + super(ReportGenerator, self).__init__() + + self.send_mail = send_mail + self.send_mail_to_admins = mail_admins + + def GetQuery(self, order=None): + """Creates a query object that will retrieve the appropriate exceptions. + + Returns: + A query to retrieve the exceptions required. + """ + q = ereporter.ExceptionRecord.all() + q.filter('date =', self.yesterday) + q.filter('major_version =', self.major_version) + if self.version_filter.lower() == 'latest': + q.filter('minor_version =', self.minor_version) + if order: + q.order(order) + return q + + def GenerateReport(self, exceptions): + """Generates an HTML exception report. + + Args: + exceptions: A list of ExceptionRecord objects. This argument will be + modified by this function. + Returns: + An HTML exception report. + """ + exceptions.sort(key=lambda e: (e.minor_version, -e.count)) + versions = [(minor, list(excs)) for minor, excs + in itertools.groupby(exceptions, lambda e: e.minor_version)] + + template_values = { + 'version_filter': self.version_filter, + 'version_count': len(versions), + + 'exception_count': sum(len(excs) for _, excs in versions), + + 'occurrence_count': sum(y.count for x in versions for y in x[1]), + 'app_id': self.app_id, + 'major_version': self.major_version, + 'date': self.yesterday, + 'versions': versions, + } + path = os.path.join(os.path.dirname(__file__), 'templates', 'report.html') + return template.render(path, template_values) + + def SendReport(self, report): + """Emails an exception report. + + Args: + report: A string containing the report to send. + """ + subject = ('Daily exception report for app "%s", major version "%s"' + % (self.app_id, self.major_version)) + report_text = saxutils.unescape(re.sub('<[^>]+>', '', report)) + mail_args = { + 'sender': self.sender, + 'subject': subject, + 'body': report_text, + 'html': report, + } + if self.to: + mail_args['to'] = self.to + self.send_mail(**mail_args) + else: + self.send_mail_to_admins(**mail_args) + + def get(self): + self.version_filter = self.request.GET.get('versions', 'all') + self.sender = self.request.GET['sender'] + self.to = self.request.GET.get('to', None) + report_date = self.request.GET.get('date', None) + if report_date: + self.yesterday = datetime.date(*[int(x) for x in report_date.split('-')]) + else: + self.yesterday = datetime.date.today() - datetime.timedelta(days=1) + self.app_id = os.environ['APPLICATION_ID'] + version = os.environ['CURRENT_VERSION_ID'] + self.major_version, self.minor_version = version.rsplit('.', 1) + self.minor_version = int(self.minor_version) + self.max_results = int(self.request.GET.get('max_results', + self.DEFAULT_MAX_RESULTS)) + self.debug = isTrue(self.request.GET.get('debug', 'false')) + self.delete = isTrue(self.request.GET.get('delete', 'true')) + + try: + exceptions = self.GetQuery(order='-minor_version').fetch(self.max_results) + except db.NeedIndexError: + exceptions = self.GetQuery().fetch(self.max_results) + + if exceptions: + report = self.GenerateReport(exceptions) + if self.debug: + self.response.out.write(report) + else: + self.SendReport(report) + + if self.delete: + db.delete(exceptions) + + +application = webapp.WSGIApplication([('.*', ReportGenerator)]) + + +def main(): + run_wsgi_app(application) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/ext/ereporter/templates/report.html b/google_appengine/google/appengine/ext/ereporter/templates/report.html new file mode 100644 index 0000000..cdf71d2 --- /dev/null +++ b/google_appengine/google/appengine/ext/ereporter/templates/report.html @@ -0,0 +1,15 @@ + +Daily exception report for app "{{app_id}}", major version "{{major_version}}". +

At least {{occurrence_count}} occurrences of {{exception_count}} exceptions across {{version_count}} versions.

+{% for version in versions %} +

Minor version {{version.0}}

+{% for exception in version.1 %} +

{{exception.signature}} (at least {{exception.count}} occurrences)

+ + + + +
Handler: {{exception.handler}}
URL: {{exception.method|escape}} {{exception.url|escape}}
Stacktrace:
{{exception.stacktrace|escape}}
+ + +{% endfor %}{% endfor %} \ No newline at end of file diff --git a/google_appengine/google/appengine/ext/gql/__init__.py b/google_appengine/google/appengine/ext/gql/__init__.py new file mode 100755 index 0000000..c30e4db --- /dev/null +++ b/google_appengine/google/appengine/ext/gql/__init__.py @@ -0,0 +1,1171 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""GQL -- the SQL-like interface to the datastore. + +Defines the GQL-based query class, which is a query mechanism +for the datastore which provides an alternative model for interacting with +data stored. +""" + + + + + +import calendar +import datetime +import logging +import re +import time + +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.api import users + +MultiQuery = datastore.MultiQuery + +LOG_LEVEL = logging.DEBUG - 1 + +_EPOCH = datetime.datetime.utcfromtimestamp(0) + +def Execute(query_string, *args, **keyword_args): + """Execute command to parse and run the query. + + Calls the query parser code to build a proto-query which is an + unbound query. The proto-query is then bound into a real query and + executed. + + Args: + query_string: properly formatted GQL query string. + args: rest of the positional arguments used to bind numeric references in + the query. + keyword_args: dictionary-based arguments (for named parameters). + + Returns: + the result of running the query with *args. + """ + app = keyword_args.pop('_app', None) + proto_query = GQL(query_string, _app=app) + return proto_query.Bind(args, keyword_args).Run() + + +class GQL(object): + """A GQL interface to the datastore. + + GQL is a SQL-like language which supports more object-like semantics + in a langauge that is familiar to SQL users. The language supported by + GQL will change over time, but will start off with fairly simple + semantics. + + - reserved words are case insensitive + - names are case sensitive + + The syntax for SELECT is fairly straightforward: + + SELECT [* | __key__ ] [FROM ] + [WHERE [AND ...]] + [ORDER BY [ASC | DESC] [, [ASC | DESC] ...]] + [LIMIT [,]] + [OFFSET ] + [HINT (ORDER_FIRST | HINT FILTER_FIRST | HINT ANCESTOR_FIRST)] + + := {< | <= | > | >= | = | != | IN} + := {< | <= | > | >= | = | != | IN} CAST() + := IN (, ...) + := ANCESTOR IS + + Currently the parser is LL(1) because of the simplicity of the grammer + (as it is largely predictive with one token lookahead). + + The class is implemented using some basic regular expression tokenization + to pull out reserved tokens and then the recursive descent parser will act + as a builder for the pre-compiled query. This pre-compiled query is then + bound to arguments before executing the query. + + Initially, three parameter passing mechanisms are supported when calling + Execute(): + + - Positional parameters + Execute('SELECT * FROM Story WHERE Author = :1 AND Date > :2') + - Named parameters + Execute('SELECT * FROM Story WHERE Author = :author AND Date > :date') + - Literals (numbers, strings, booleans, and NULL) + Execute('SELECT * FROM Story WHERE Author = \'James\'') + + Users are also given the option of doing type conversions to other datastore + types (e.g. db.Email, db.GeoPt). The language provides a conversion function + which allows the caller to express conversions of both literals and + parameters. The current conversion operators are: + - GEOPT(float, float) + - USER(str) + - KEY(kind, id/name[, kind, id/name...]) + - DATETIME(year, month, day, hour, minute, second) + - DATETIME('YYYY-MM-DD HH:MM:SS') + - DATE(year, month, day) + - DATE('YYYY-MM-DD') + - TIME(hour, minute, second) + - TIME('HH:MM:SS') + + We will properly serialize and quote all values. + + It should also be noted that there are some caveats to the queries that can + be expressed in the syntax. The parser will attempt to make these clear as + much as possible, but some of the caveats include: + - There is no OR operation. In most cases, you should prefer to use IN to + express the idea of wanting data matching one of a set of values. + - You cannot express inequality operators on multiple different properties + - You can only have one != operator per query (related to the previous + rule). + - The IN and != operators must be used carefully because they can + dramatically raise the amount of work done by the datastore. As such, + there is a limit on the number of elements you can use in IN statements. + This limit is set fairly low. Currently, a max of 30 datastore queries is + allowed in a given GQL query. != translates into 2x the number of + datastore queries, and IN multiplies by the number of elements in the + clause (so having two IN clauses, one with 5 elements, the other with 6 + will cause 30 queries to occur). + - Literals can take the form of basic types or as type-cast literals. On + the other hand, literals within lists can currently only take the form of + simple types (strings, integers, floats). + + + SELECT * will return an iterable set of entities; SELECT __key__ will return + an iterable set of Keys. + """ + + TOKENIZE_REGEX = re.compile(r""" + (?:'[^'\n\r]*')+| + <=|>=|!=|=|<|>| + :\w+| + ,| + \*| + -?\d+(?:\.\d+)?| + \w+| + \(|\)| + \S+ + """, re.VERBOSE | re.IGNORECASE) + + MAX_ALLOWABLE_QUERIES = datastore.MAX_ALLOWABLE_QUERIES + + __ANCESTOR = -1 + + def __init__(self, query_string, _app=None, _auth_domain=None, + namespace=None): + """Ctor. + + Parses the input query into the class as a pre-compiled query, allowing + for a later call to Bind() to bind arguments as defined in the + documentation. + + Args: + query_string: properly formatted GQL query string. + namespace: the namespace to use for this query. + + Raises: + datastore_errors.BadQueryError: if the query is not parsable. + """ + self._entity = '' + self.__filters = {} + self.__has_ancestor = False + self.__orderings = [] + self.__offset = -1 + self.__limit = -1 + self.__hint = '' + self.__app = _app + self.__namespace = namespace + self.__auth_domain = _auth_domain + + self.__symbols = self.TOKENIZE_REGEX.findall(query_string) + self.__next_symbol = 0 + if not self.__Select(): + raise datastore_errors.BadQueryError( + 'Unable to parse query') + else: + pass + + def Bind(self, args, keyword_args, cursor=None, end_cursor=None): + """Bind the existing query to the argument list. + + Assumes that the input args are first positional, then a dictionary. + So, if the query contains references to :1, :2 and :name, it is assumed + that arguments are passed as (:1, :2, dict) where dict contains a mapping + [name] -> value. + + Args: + args: the arguments to bind to the object's unbound references. + keyword_args: dictionary-based arguments (for named parameters). + + Raises: + datastore_errors.BadArgumentError: when arguments are left unbound + (missing from the inputs arguments) or when arguments do not match the + expected type. + + Returns: + The bound datastore.Query object. This may take the form of a MultiQuery + object if the GQL query will require multiple backend queries to statisfy. + """ + num_args = len(args) + input_args = frozenset(xrange(num_args)) + used_args = set() + + queries = [] + enumerated_queries = self.EnumerateQueries(used_args, args, keyword_args) + if enumerated_queries: + query_count = len(enumerated_queries) + else: + query_count = 1 + + for i in xrange(query_count): + queries.append(datastore.Query(self._entity, + _app=self.__app, + keys_only=self._keys_only, + namespace=self.__namespace, + cursor=cursor, + end_cursor=end_cursor)) + + logging.log(LOG_LEVEL, + 'Binding with %i positional args %s and %i keywords %s' + , len(args), args, len(keyword_args), keyword_args) + for ((identifier, condition), value_list) in self.__filters.iteritems(): + for (operator, params) in value_list: + value = self.__Operate(args, keyword_args, used_args, operator, params) + if not self.__IsMultiQuery(condition): + for query in queries: + self.__AddFilterToQuery(identifier, condition, value, query) + + unused_args = input_args - used_args + if unused_args: + unused_values = [unused_arg + 1 for unused_arg in unused_args] + raise datastore_errors.BadArgumentError('Unused positional arguments %s' % + unused_values) + + if enumerated_queries: + logging.log(LOG_LEVEL, + 'Multiple Queries Bound: %s', + enumerated_queries) + + for (query, enumerated_query) in zip(queries, enumerated_queries): + query.update(enumerated_query) + + if self.__orderings: + for query in queries: + query.Order(*tuple(self.__orderings)) + + if query_count > 1: + return MultiQuery(queries, self.__orderings) + else: + return queries[0] + + def EnumerateQueries(self, used_args, args, keyword_args): + """Create a list of all multi-query filter combinations required. + + To satisfy multi-query requests ("IN" and "!=" filters), multiple queries + may be required. This code will enumerate the power-set of all multi-query + filters. + + Args: + used_args: set of used positional parameters (output only variable used in + reporting for unused positional args) + args: positional arguments referenced by the proto-query in self. This + assumes the input is a tuple (and can also be called with a varargs + param). + keyword_args: dict of keyword arguments referenced by the proto-query in + self. + + Returns: + A list of maps [(identifier, condition) -> value] of all queries needed + to satisfy the GQL query with the given input arguments. + """ + enumerated_queries = [] + + for ((identifier, condition), value_list) in self.__filters.iteritems(): + for (operator, params) in value_list: + value = self.__Operate(args, keyword_args, used_args, operator, params) + self.__AddMultiQuery(identifier, condition, value, enumerated_queries) + + return enumerated_queries + + def __CastError(self, operator, values, error_message): + """Query building error for type cast operations. + + Args: + operator: the failed cast operation + values: value list passed to the cast operator + error_message: string to emit as part of the 'Cast Error' string. + + Raises: + BadQueryError and passes on an error message from the caller. Will raise + BadQueryError on all calls. + """ + raise datastore_errors.BadQueryError( + 'Type Cast Error: unable to cast %r with operation %s (%s)' % + (values, operator.upper(), error_message)) + + def __CastNop(self, values): + """Return values[0] if it exists -- default for most where clauses.""" + if len(values) != 1: + self.__CastError(values, 'nop', 'requires one and only one value') + else: + return values[0] + + def __CastList(self, values): + """Return the full list of values -- only useful for IN clause.""" + if values: + return values + else: + return None + + def __CastKey(self, values): + """Cast input values to Key() class using encoded string or tuple list.""" + if not len(values) % 2: + return datastore_types.Key.from_path(_app=self.__app, + namespace=self.__namespace, + *values) + elif len(values) == 1 and isinstance(values[0], basestring): + return datastore_types.Key(values[0]) + else: + self.__CastError('KEY', values, + 'requires an even number of operands ' + 'or a single encoded string') + + def __CastGeoPt(self, values): + """Cast input to GeoPt() class using 2 input parameters.""" + if len(values) != 2: + self.__CastError('GEOPT', values, 'requires 2 input parameters') + return datastore_types.GeoPt(*values) + + def __CastUser(self, values): + """Cast to User() class using the email address in values[0].""" + if len(values) != 1: + self.__CastError('user', values, 'requires one and only one value') + elif values[0] is None: + self.__CastError('user', values, 'must be non-null') + else: + return users.User(email=values[0], _auth_domain=self.__auth_domain) + + def __EncodeIfNeeded(self, value): + """Simple helper function to create an str from possibly unicode strings. + Args: + value: input string (should pass as an instance of str or unicode). + """ + if isinstance(value, unicode): + return value.encode('utf8') + else: + return value + + def __CastDate(self, values): + """Cast DATE values (year/month/day) from input (to datetime.datetime). + + Casts DATE input values formulated as ISO string or time tuple inputs. + + Args: + values: either a single string with ISO time representation or 3 + integer valued date tuple (year, month, day). + + Returns: + datetime.datetime value parsed from the input values. + """ + + if len(values) == 1: + value = self.__EncodeIfNeeded(values[0]) + if isinstance(value, str): + try: + time_tuple = time.strptime(value, '%Y-%m-%d')[0:6] + except ValueError, err: + self.__CastError('DATE', values, err) + else: + self.__CastError('DATE', values, 'Single input value not a string') + elif len(values) == 3: + time_tuple = (values[0], values[1], values[2], 0, 0, 0) + else: + self.__CastError('DATE', values, + 'function takes 1 string or 3 integer values') + + try: + return datetime.datetime(*time_tuple) + except ValueError, err: + self.__CastError('DATE', values, err) + + def __CastTime(self, values): + """Cast TIME values (hour/min/sec) from input (to datetime.datetime). + + Casts TIME input values formulated as ISO string or time tuple inputs. + + Args: + values: either a single string with ISO time representation or 1-4 + integer valued time tuple (hour), (hour, minute), + (hour, minute, second), (hour, minute, second, microsec). + + Returns: + datetime.datetime value parsed from the input values. + """ + if len(values) == 1: + value = self.__EncodeIfNeeded(values[0]) + if isinstance(value, str): + try: + time_tuple = time.strptime(value, '%H:%M:%S') + except ValueError, err: + self.__CastError('TIME', values, err) + time_tuple = (1970, 1, 1) + time_tuple[3:] + time_tuple = time_tuple[0:6] + elif isinstance(value, int): + time_tuple = (1970, 1, 1, value) + else: + self.__CastError('TIME', values, + 'Single input value not a string or integer hour') + elif len(values) <= 4: + time_tuple = (1970, 1, 1) + tuple(values) + else: + self.__CastError('TIME', values, + 'function takes 1 to 4 integers or 1 string') + + try: + return datetime.datetime(*time_tuple) + except ValueError, err: + self.__CastError('TIME', values, err) + + def __CastDatetime(self, values): + """Cast DATETIME values (string or tuple) from input (to datetime.datetime). + + Casts DATETIME input values formulated as ISO string or datetime tuple + inputs. + + Args: + values: either a single string with ISO representation or 3-7 + integer valued time tuple (year, month, day, ...). + + Returns: + datetime.datetime value parsed from the input values. + """ + if len(values) == 1: + value = self.__EncodeIfNeeded(values[0]) + if isinstance(value, str): + try: + time_tuple = time.strptime(str(value), '%Y-%m-%d %H:%M:%S')[0:6] + except ValueError, err: + self.__CastError('DATETIME', values, err) + else: + self.__CastError('DATETIME', values, 'Single input value not a string') + else: + time_tuple = values + + try: + return datetime.datetime(*time_tuple) + except ValueError, err: + self.__CastError('DATETIME', values, err) + + def __Operate(self, args, keyword_args, used_args, operator, params): + """Create a single output value from params using the operator string given. + + Args: + args,keyword_args: arguments passed in for binding purposes (used in + binding positional and keyword based arguments). + used_args: set of numeric arguments accessed in this call. + values are ints representing used zero-based positional arguments. + used as an output parameter with new used arguments appended to the + list. + operator: string representing the operator to use 'nop' just returns + the first value from params. + params: parameter list to operate on (positional references, named + references, or literals). + + Returns: + A value which can be used as part of a GQL filter description (either a + list of datastore types -- for use with IN, or a single datastore type -- + for use with other filters). + """ + if not params: + return None + + param_values = [] + for param in params: + if isinstance(param, Literal): + value = param.Get() + else: + value = self.__GetParam(param, args, keyword_args) + if isinstance(param, int): + used_args.add(param - 1) + logging.log(LOG_LEVEL, 'found param for bind: %s value: %s', + param, value) + param_values.append(value) + + logging.log(LOG_LEVEL, '%s Operating on values: %s', + operator, repr(param_values)) + + if operator in self.__cast_operators: + result = self.__cast_operators[operator](self, param_values) + else: + self.__Error('Operation %s is invalid' % operator) + + return result + + def __IsMultiQuery(self, condition): + """Return whether or not this condition could require multiple queries.""" + return condition.lower() in ('in', '!=') + + def __GetParam(self, reference, args, keyword_args): + """Get the specified parameter from the input arguments. + + Args: + reference: id for a filter reference in the filter list (string or + number) + args: positional args passed in by the user (tuple of arguments, indexed + numerically by "reference") + keyword_args: dict of keyword based arguments (strings in "reference") + + Returns: + The specified param from the input list. + + Raises: + BadArgumentError if the referenced argument doesn't exist. + """ + num_args = len(args) + if isinstance(reference, int): + if reference <= num_args: + return args[reference - 1] + else: + raise datastore_errors.BadArgumentError( + 'Missing argument for bind, requires argument #%i, ' + 'but only has %i args.' % (reference, num_args)) + elif isinstance(reference, basestring): + if reference in keyword_args: + return keyword_args[reference] + else: + raise datastore_errors.BadArgumentError( + 'Missing named arguments for bind, requires argument %s' % + reference) + else: + assert False, 'Unknown reference %s' % reference + + def __AddMultiQuery(self, identifier, condition, value, enumerated_queries): + """Helper function to add a muti-query to previously enumerated queries. + + Args: + identifier: property being filtered by this condition + condition: filter condition (e.g. !=,in) + value: value being bound + enumerated_queries: in/out list of already bound queries -> expanded list + with the full enumeration required to satisfy the condition query + Raises: + BadArgumentError if the filter is invalid (namely non-list with IN) + """ + if condition.lower() in ('!=', 'in') and self._keys_only: + raise datastore_errors.BadQueryError( + 'Keys only queries do not support IN or != filters.') + + def CloneQueries(queries, n): + """Do a full copy of the queries and append to the end of the queries. + + Does an in-place replication of the input list and sorts the result to + put copies next to one-another. + + Args: + queries: list of all filters to clone + n: number of copies to make + + Returns: + Number of iterations needed to fill the structure + """ + if not enumerated_queries: + for i in xrange(n): + queries.append({}) + return 1 + else: + old_size = len(queries) + tmp_queries = [] + for i in xrange(n - 1): + [tmp_queries.append(filter_map.copy()) for filter_map in queries] + queries.extend(tmp_queries) + queries.sort() + return old_size + + if condition == '!=': + if len(enumerated_queries) * 2 > self.MAX_ALLOWABLE_QUERIES: + raise datastore_errors.BadArgumentError( + 'Cannot satisfy query -- too many IN/!= values.') + + num_iterations = CloneQueries(enumerated_queries, 2) + for i in xrange(num_iterations): + enumerated_queries[2 * i]['%s <' % identifier] = value + enumerated_queries[2 * i + 1]['%s >' % identifier] = value + elif condition.lower() == 'in': + if not isinstance(value, list): + raise datastore_errors.BadArgumentError('List expected for "IN" filter') + + in_list_size = len(value) + if len(enumerated_queries) * in_list_size > self.MAX_ALLOWABLE_QUERIES: + raise datastore_errors.BadArgumentError( + 'Cannot satisfy query -- too many IN/!= values.') + + num_iterations = CloneQueries(enumerated_queries, in_list_size) + for clone_num in xrange(num_iterations): + for value_num in xrange(len(value)): + list_val = value[value_num] + query_num = in_list_size * clone_num + value_num + filt = '%s =' % identifier + enumerated_queries[query_num][filt] = list_val + + def __AddFilterToQuery(self, identifier, condition, value, query): + """Add a filter condition to a query based on the inputs. + + Args: + identifier: name of the property (or self.__ANCESTOR for ancestors) + condition: test condition + value: test value passed from the caller + query: query to add the filter to + """ + if identifier != self.__ANCESTOR: + filter_condition = '%s %s' % (identifier, condition) + logging.log(LOG_LEVEL, 'Setting filter on "%s" with value "%s"', + filter_condition, value.__class__) + datastore._AddOrAppend(query, filter_condition, value) + + else: + logging.log(LOG_LEVEL, 'Setting ancestor query for ancestor %s', value) + query.Ancestor(value) + + def Run(self, *args, **keyword_args): + """Runs this query. + + Similar to datastore.Query.Run. + Assumes that limit == -1 or > 0 + + Args: + args: arguments used to bind to references in the compiled query object. + keyword_args: dictionary-based arguments (for named parameters). + + Returns: + A list of results if a query count limit was passed. + A result iterator if no limit was given. + """ + bind_results = self.Bind(args, keyword_args) + + offset = self.offset() + + if self.__limit == -1: + it = bind_results.Run() + try: + for i in xrange(offset): + it.next() + except StopIteration: + pass + + return it + else: + res = bind_results.Get(self.__limit, offset) + return res + + def filters(self): + """Return the compiled list of filters.""" + return self.__filters + + def hint(self): + """Return the datastore hint.""" + return self.__hint + + def limit(self): + """Return numerical result count limit.""" + return self.__limit + + def offset(self): + """Return numerical result offset.""" + if self.__offset == -1: + return 0 + else: + return self.__offset + + def orderings(self): + """Return the result ordering list.""" + return self.__orderings + + def is_keys_only(self): + """Returns True if this query returns Keys, False if it returns Entities.""" + return self._keys_only + + __iter__ = Run + + __result_type_regex = re.compile(r'(\*|__key__)') + __quoted_string_regex = re.compile(r'((?:\'[^\'\n\r]*\')+)') + __ordinal_regex = re.compile(r':(\d+)$') + __named_regex = re.compile(r':(\w+)$') + __identifier_regex = re.compile(r'(\w+)$') + __conditions_regex = re.compile(r'(<=|>=|!=|=|<|>|is|in)$', re.IGNORECASE) + __number_regex = re.compile(r'(\d+)$') + __cast_regex = re.compile( + r'(geopt|user|key|date|time|datetime)$', re.IGNORECASE) + __cast_operators = { + 'geopt': __CastGeoPt, + 'user': __CastUser, + 'key': __CastKey, + 'datetime': __CastDatetime, + 'date': __CastDate, + 'time': __CastTime, + 'list': __CastList, + 'nop': __CastNop, + } + + def __Error(self, error_message): + """Generic query error. + + Args: + error_message: string to emit as part of the 'Parse Error' string. + + Raises: + BadQueryError and passes on an error message from the caller. Will raise + BadQueryError on all calls to __Error() + """ + if self.__next_symbol >= len(self.__symbols): + raise datastore_errors.BadQueryError( + 'Parse Error: %s at end of string' % error_message) + else: + raise datastore_errors.BadQueryError( + 'Parse Error: %s at symbol %s' % + (error_message, self.__symbols[self.__next_symbol])) + + def __Accept(self, symbol_string): + """Advance the symbol and return true iff the next symbol matches input.""" + if self.__next_symbol < len(self.__symbols): + logging.log(LOG_LEVEL, '\t%s', self.__symbols) + logging.log(LOG_LEVEL, '\tExpect: %s Got: %s', + symbol_string, self.__symbols[self.__next_symbol].upper()) + if self.__symbols[self.__next_symbol].upper() == symbol_string: + self.__next_symbol += 1 + return True + return False + + def __Expect(self, symbol_string): + """Require that the next symbol matches symbol_string, or emit an error. + + Args: + symbol_string: next symbol expected by the caller + + Raises: + BadQueryError if the next symbol doesn't match the parameter passed in. + """ + if not self.__Accept(symbol_string): + self.__Error('Unexpected Symbol: %s' % symbol_string) + + def __AcceptRegex(self, regex): + """Advance and return the symbol if the next symbol matches the regex. + + Args: + regex: the compiled regular expression to attempt acceptance on. + + Returns: + The first group in the expression to allow for convenient access + to simple matches. Requires () around some objects in the regex. + None if no match is found. + """ + if self.__next_symbol < len(self.__symbols): + match_symbol = self.__symbols[self.__next_symbol] + logging.log(LOG_LEVEL, '\taccept %s on symbol %s', regex, match_symbol) + match = regex.match(match_symbol) + if match: + self.__next_symbol += 1 + if match.groups(): + matched_string = match.group(1) + + logging.log(LOG_LEVEL, '\taccepted %s', matched_string) + return matched_string + + return None + + def __AcceptTerminal(self): + """Only accept an empty string. + + Returns: + True + + Raises: + BadQueryError if there are unconsumed symbols in the query. + """ + if self.__next_symbol < len(self.__symbols): + self.__Error('Expected no additional symbols') + return True + + def __Select(self): + """Consume the SELECT clause and everything that follows it. + + Assumes SELECT * to start. + Transitions to a FROM clause. + + Returns: + True if parsing completed okay. + """ + self.__Expect('SELECT') + result_type = self.__AcceptRegex(self.__result_type_regex) + self._keys_only = (result_type == '__key__') + return self.__From() + + def __From(self): + """Consume the FROM clause. + + Assumes a single well formed entity in the clause. + Assumes FROM + Transitions to a WHERE clause. + + Returns: + True if parsing completed okay. + """ + if self.__Accept('FROM'): + kind = self.__AcceptRegex(self.__identifier_regex) + if kind: + self._entity = kind + else: + self.__Error('Identifier Expected') + return False + else: + self._entity = None + return self.__Where() + + def __Where(self): + """Consume the WHERE cluase. + + These can have some recursion because of the AND symbol. + + Returns: + True if parsing the WHERE clause completed correctly, as well as all + subsequent clauses + """ + if self.__Accept('WHERE'): + return self.__FilterList() + return self.__OrderBy() + + def __FilterList(self): + """Consume the filter list (remainder of the WHERE clause).""" + identifier = self.__AcceptRegex(self.__identifier_regex) + if not identifier: + self.__Error('Invalid WHERE Identifier') + return False + + condition = self.__AcceptRegex(self.__conditions_regex) + if not condition: + self.__Error('Invalid WHERE Condition') + return False + self.__CheckFilterSyntax(identifier, condition) + + if not self.__AddSimpleFilter(identifier, condition, self.__Reference()): + if not self.__AddSimpleFilter(identifier, condition, self.__Literal()): + type_cast = self.__TypeCast() + if (not type_cast or + not self.__AddProcessedParameterFilter(identifier, condition, + *type_cast)): + self.__Error('Invalid WHERE condition') + + if self.__Accept('AND'): + return self.__FilterList() + + return self.__OrderBy() + + def __GetValueList(self): + """Read in a list of parameters from the tokens and return the list. + + Reads in a set of tokens, but currently only accepts literals, positional + parameters, or named parameters. Or empty list if nothing was parsed. + + Returns: + A list of values parsed from the input, with values taking the form of + strings (unbound, named reference), integers (unbound, positional + reference), or Literal() (bound value usable directly as part of a filter + with no additional information). + """ + params = [] + + while True: + reference = self.__Reference() + if reference: + params.append(reference) + else: + literal = self.__Literal() + if literal: + params.append(literal) + else: + self.__Error('Parameter list requires literal or reference parameter') + + if not self.__Accept(','): + break + + return params + + def __CheckFilterSyntax(self, identifier, condition): + """Check that filter conditions are valid and throw errors if not. + + Args: + identifier: identifier being used in comparison + condition: string form of the comparison operator used in the filter + """ + if identifier.lower() == 'ancestor': + if condition.lower() == 'is': + if self.__has_ancestor: + self.__Error('Only one ANCESTOR IS" clause allowed') + else: + self.__Error('"IS" expected to follow "ANCESTOR"') + elif condition.lower() == 'is': + self.__Error('"IS" can only be used when comparing against "ANCESTOR"') + + def __AddProcessedParameterFilter(self, identifier, condition, + operator, parameters): + """Add a filter with post-processing required. + + Args: + identifier: property being compared. + condition: comparison operation being used with the property (e.g. !=). + operator: operation to perform on the parameters before adding the filter. + parameters: list of bound parameters passed to 'operator' before creating + the filter. When using the parameters as a pass-through, pass 'nop' + into the operator field and the first value will be used unprocessed). + + Returns: + True if the filter was okay to add. + """ + if parameters is None: + return False + if parameters[0] is None: + return False + + logging.log(LOG_LEVEL, 'Adding Filter %s %s %s', + identifier, condition, repr(parameters)) + filter_rule = (identifier, condition) + if identifier.lower() == 'ancestor': + self.__has_ancestor = True + filter_rule = (self.__ANCESTOR, 'is') + assert condition.lower() == 'is' + + if operator == 'list' and condition.lower() != 'in': + self.__Error('Only IN can process a list of values') + + self.__filters.setdefault(filter_rule, []).append((operator, parameters)) + return True + + def __AddSimpleFilter(self, identifier, condition, parameter): + """Add a filter to the query being built (no post-processing on parameter). + + Args: + identifier: identifier being used in comparison + condition: string form of the comparison operator used in the filter + parameter: ID of the reference being made or a value of type Literal + + Returns: + True if the filter could be added. + False otherwise. + """ + return self.__AddProcessedParameterFilter(identifier, condition, + 'nop', [parameter]) + + def __Reference(self): + """Consume a parameter reference and return it. + + Consumes a reference to a positional parameter (:1) or a named parameter + (:email). Only consumes a single reference (not lists). + + Returns: + The name of the reference (integer for positional parameters or string + for named parameters) to a bind-time parameter. + """ + logging.log(LOG_LEVEL, 'Try Reference') + reference = self.__AcceptRegex(self.__ordinal_regex) + if reference: + return int(reference) + else: + reference = self.__AcceptRegex(self.__named_regex) + if reference: + return reference + + return None + + def __Literal(self): + """Parse literals from our token list. + + Returns: + The parsed literal from the input string (currently either a string, + integer, or floating point value). + """ + logging.log(LOG_LEVEL, 'Try Literal') + literal = None + try: + literal = int(self.__symbols[self.__next_symbol]) + except ValueError: + pass + else: + self.__next_symbol += 1 + + if literal is None: + try: + literal = float(self.__symbols[self.__next_symbol]) + except ValueError: + pass + else: + self.__next_symbol += 1 + + if literal is None: + literal = self.__AcceptRegex(self.__quoted_string_regex) + if literal: + literal = literal[1:-1].replace("''", "'") + + if literal is None: + if self.__Accept('TRUE'): + literal = True + elif self.__Accept('FALSE'): + literal = False + + if literal is not None: + return Literal(literal) + + if self.__Accept('NULL'): + return Literal(None) + else: + return None + + def __TypeCast(self): + """Check if the next operation is a type-cast and return the cast if so. + + Casting operators look like simple function calls on their parameters. This + code returns the cast operator found and the list of parameters provided by + the user to complete the cast operation. + + Returns: + A tuple (cast operator, params) which represents the cast operation + requested and the parameters parsed from the cast clause. + + None - if there is no TypeCast function. + """ + logging.log(LOG_LEVEL, 'Try Type Cast') + cast_op = self.__AcceptRegex(self.__cast_regex) + if not cast_op: + if self.__Accept('('): + cast_op = 'list' + else: + return None + else: + cast_op = cast_op.lower() + self.__Expect('(') + + params = self.__GetValueList() + self.__Expect(')') + + logging.log(LOG_LEVEL, 'Got casting operator %s with params %s', + cast_op, repr(params)) + return (cast_op, params) + + def __OrderBy(self): + """Consume the ORDER BY clause.""" + if self.__Accept('ORDER'): + self.__Expect('BY') + return self.__OrderList() + return self.__Limit() + + def __OrderList(self): + """Consume variables and sort order for ORDER BY clause.""" + identifier = self.__AcceptRegex(self.__identifier_regex) + if identifier: + if self.__Accept('DESC'): + self.__orderings.append((identifier, datastore.Query.DESCENDING)) + elif self.__Accept('ASC'): + self.__orderings.append((identifier, datastore.Query.ASCENDING)) + else: + self.__orderings.append((identifier, datastore.Query.ASCENDING)) + else: + self.__Error('Invalid ORDER BY Property') + + logging.log(LOG_LEVEL, self.__orderings) + if self.__Accept(','): + return self.__OrderList() + return self.__Limit() + + def __Limit(self): + """Consume the LIMIT clause.""" + if self.__Accept('LIMIT'): + maybe_limit = self.__AcceptRegex(self.__number_regex) + + if maybe_limit: + if self.__Accept(','): + self.__offset = int(maybe_limit) + if self.__offset < 0: + self.__Error('Bad offset in LIMIT Value') + else: + logging.log(LOG_LEVEL, 'Set offset to %i', self.__offset) + maybe_limit = self.__AcceptRegex(self.__number_regex) + + self.__limit = int(maybe_limit) + if self.__limit < 1: + self.__Error('Bad Limit in LIMIT Value') + else: + logging.log(LOG_LEVEL, 'Set limit to %i', self.__limit) + else: + self.__Error('Non-number limit in LIMIT clause') + + return self.__Offset() + + def __Offset(self): + """Consume the OFFSET clause.""" + if self.__Accept('OFFSET'): + if self.__offset != -1: + self.__Error('Offset already defined in LIMIT clause') + + offset = self.__AcceptRegex(self.__number_regex) + + if offset: + self.__offset = int(offset) + if self.__offset < 0: + self.__Error('Bad offset in OFFSET clause') + else: + logging.log(LOG_LEVEL, 'Set offset to %i', self.__offset) + else: + self.__Error('Non-number offset in OFFSET clause') + + return self.__Hint() + + def __Hint(self): + """Consume the HINT clause. + + Requires one of three options (mirroring the rest of the datastore): + HINT ORDER_FIRST + HINT ANCESTOR_FIRST + HINT FILTER_FIRST + + Returns: + True if the hint clause and later clauses all parsed okay + """ + if self.__Accept('HINT'): + if self.__Accept('ORDER_FIRST'): + self.__hint = 'ORDER_FIRST' + elif self.__Accept('FILTER_FIRST'): + self.__hint = 'FILTER_FIRST' + elif self.__Accept('ANCESTOR_FIRST'): + self.__hint = 'ANCESTOR_FIRST' + else: + self.__Error('Unknown HINT') + return False + return self.__AcceptTerminal() + + +class Literal(object): + """Class for representing literal values in a way unique from unbound params. + + This is a simple wrapper class around basic types and datastore types. + """ + + def __init__(self, value): + self.__value = value + + def Get(self): + """Return the value of the literal.""" + return self.__value + + def __repr__(self): + return 'Literal(%s)' % repr(self.__value) diff --git a/google_appengine/google/appengine/ext/key_range/__init__.py b/google_appengine/google/appengine/ext/key_range/__init__.py new file mode 100755 index 0000000..105db7d --- /dev/null +++ b/google_appengine/google/appengine/ext/key_range/__init__.py @@ -0,0 +1,617 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Key range representation and splitting.""" + + +import os + +try: + import simplejson +except ImportError: + simplejson = None + +from google.appengine.api import datastore +from google.appengine.datastore import datastore_pb +from google.appengine.ext import db + + +class Error(Exception): + """Base class for exceptions in this module.""" + + +class KeyRangeError(Error): + """Error while trying to generate a KeyRange.""" + + +class SimplejsonUnavailableError(Error): + """Error while using json functionality whith unavailable simplejson.""" + + +class KeyRange(object): + """Represents a range of keys in the datastore. + + A KeyRange object represents a key range + (key_start, include_start, key_end, include_end) + and a scan direction (KeyRange.DESC or KeyRange.ASC). + """ + + DESC = "DESC" + ASC = "ASC" + + def __init__(self, + key_start=None, + key_end=None, + direction=None, + include_start=True, + include_end=True, + _app=None): + """Initialize a KeyRange object. + + Args: + key_start: The starting key for this range. + key_end: The ending key for this range. + direction: The direction of the query for this range. + include_start: Whether the start key should be included in the range. + include_end: Whether the end key should be included in the range. + """ + if direction is None: + direction = KeyRange.ASC + assert direction in (KeyRange.ASC, KeyRange.DESC) + self.direction = direction + self.key_start = key_start + self.key_end = key_end + self.include_start = include_start + self.include_end = include_end + self._app = _app + + def __str__(self): + if self.include_start: + left_side = "[" + else: + left_side = "(" + if self.include_end: + right_side = "]" + else: + right_side = "(" + return "%s%s%s to %s%s" % (self.direction, left_side, repr(self.key_start), + repr(self.key_end), right_side) + + def __repr__(self): + return ("key_range.KeyRange(key_start=%s,key_end=%s,direction=%s," + "include_start=%s,include_end=%s)") % (repr(self.key_start), + repr(self.key_end), + repr(self.direction), + repr(self.include_start), + repr(self.include_end)) + + def advance(self, key): + """Updates the start of the range immediately past the specified key. + + Args: + key: A db.Key. + """ + self.include_start = False + self.key_start = key + + def filter_query(self, query): + """Add query filter to restrict to this key range. + + Args: + query: A db.Query instance. + + Returns: + The input query restricted to this key range. + """ + assert isinstance(query, db.Query) + if self.include_start: + start_comparator = ">=" + else: + start_comparator = ">" + if self.include_end: + end_comparator = "<=" + else: + end_comparator = "<" + if self.key_start: + query.filter("__key__ %s" % start_comparator, self.key_start) + if self.key_end: + query.filter("__key__ %s" % end_comparator, self.key_end) + return query + + def filter_datastore_query(self, query): + """Add query filter to restrict to this key range. + + Args: + query: A datastore.Query instance. + + Returns: + The input query restricted to this key range. + """ + assert isinstance(query, datastore.Query) + if self._app: + query.__app = self._app + if self.include_start: + start_comparator = ">=" + else: + start_comparator = ">" + if self.include_end: + end_comparator = "<=" + else: + end_comparator = "<" + if self.key_start: + query.update({"__key__ %s" % start_comparator: self.key_start}) + if self.key_end: + query.update({"__key__ %s" % end_comparator: self.key_end}) + return query + + def __get_direction(self, asc, desc): + """Check that self.direction is in (KeyRange.ASC, KeyRange.DESC). + + Args: + asc: Argument to return if self.direction is KeyRange.ASC + desc: Argument to return if self.direction is KeyRange.DESC + + Returns: + asc or desc appropriately + + Raises: + KeyRangeError: if self.direction is not in (KeyRange.ASC, KeyRange.DESC). + """ + if self.direction == KeyRange.ASC: + return asc + elif self.direction == KeyRange.DESC: + return desc + else: + raise KeyRangeError("KeyRange direction unexpected: %s", self.direction) + + def make_directed_query(self, kind_class, keys_only=False): + """Construct a query for this key range, including the scan direction. + + Args: + kind_class: A kind implementation class. + keys_only: bool, default False, use keys_only on Query? + + Returns: + A db.Query instance. + + Raises: + KeyRangeError: if self.direction is not in (KeyRange.ASC, KeyRange.DESC). + """ + direction = self.__get_direction("", "-") + query = db.Query(kind_class, keys_only=keys_only) + query.order("%s__key__" % direction) + + query = self.filter_query(query) + return query + + def make_directed_datastore_query(self, kind, keys_only=False): + """Construct a query for this key range, including the scan direction. + + Args: + kind: A string. + keys_only: bool, default False, use keys_only on Query? + + Returns: + A datastore.Query instance. + + Raises: + KeyRangeError: if self.direction is not in (KeyRange.ASC, KeyRange.DESC). + """ + direction = self.__get_direction(datastore.Query.ASCENDING, + datastore.Query.DESCENDING) + query = datastore.Query(kind, _app=self._app, keys_only=keys_only) + query.Order(("__key__", direction)) + + query = self.filter_datastore_query(query) + return query + + def make_ascending_query(self, kind_class, keys_only=False): + """Construct a query for this key range without setting the scan direction. + + Args: + kind_class: A kind implementation class. + keys_only: bool, default False, query only for keys. + + Returns: + A db.Query instance. + """ + query = db.Query(kind_class, keys_only=keys_only) + query.order("__key__") + + query = self.filter_query(query) + return query + + def make_ascending_datastore_query(self, kind, keys_only=False): + """Construct a query for this key range without setting the scan direction. + + Args: + kind: A string. + keys_only: bool, default False, use keys_only on Query? + + Returns: + A datastore.Query instance. + """ + query = datastore.Query(kind, _app=self._app, keys_only=keys_only) + query.Order(("__key__", datastore.Query.ASCENDING)) + + query = self.filter_datastore_query(query) + return query + + def split_range(self, batch_size=0): + """Split this key range into a list of at most two ranges. + + This method attempts to split the key range approximately in half. + Numeric ranges are split in the middle into two equal ranges and + string ranges are split lexicographically in the middle. If the + key range is smaller than batch_size it is left unsplit. + + Note that splitting is done without knowledge of the distribution + of actual entities in the key range, so there is no guarantee (nor + any particular reason to believe) that the entities of the range + are evenly split. + + Args: + batch_size: The maximum size of a key range that should not be split. + + Returns: + A list of one or two key ranges covering the same space as this range. + """ + key_start = self.key_start + key_end = self.key_end + include_start = self.include_start + include_end = self.include_end + + key_pairs = [] + if not key_start: + key_pairs.append((key_start, include_start, key_end, include_end, + KeyRange.ASC)) + elif not key_end: + key_pairs.append((key_start, include_start, key_end, include_end, + KeyRange.DESC)) + else: + key_split = KeyRange.split_keys(key_start, key_end, batch_size) + first_include_end = True + if key_split == key_start: + first_include_end = first_include_end and include_start + + key_pairs.append((key_start, include_start, + key_split, first_include_end, + KeyRange.DESC)) + + second_include_end = include_end + if key_split == key_end: + second_include_end = False + key_pairs.append((key_split, False, + key_end, second_include_end, + KeyRange.ASC)) + + ranges = [KeyRange(key_start=start, + include_start=include_start, + key_end=end, + include_end=include_end, + direction=direction, + _app=self._app) + for (start, include_start, end, include_end, direction) + in key_pairs] + + return ranges + + def __cmp__(self, other): + """Compare two key ranges. + + Key ranges with a value of None for key_start or key_end, are always + considered to have include_start=False or include_end=False, respectively, + when comparing. Since None indicates an unbounded side of the range, + the include specifier is meaningless. The ordering generated is total + but somewhat arbitrary. + + Args: + other: An object to compare to this one. + + Returns: + -1: if this key range is less than other. + 0: if this key range is equal to other. + 1: if this key range is greater than other. + """ + if not isinstance(other, KeyRange): + return 1 + + self_list = [self.key_start, self.key_end, self.direction, + self.include_start, self.include_end] + if not self.key_start: + self_list[3] = False + if not self.key_end: + self_list[4] = False + + other_list = [other.key_start, + other.key_end, + other.direction, + other.include_start, + other.include_end] + if not other.key_start: + other_list[3] = False + if not other.key_end: + other_list[4] = False + + return cmp(self_list, other_list) + + @staticmethod + def bisect_string_range(start, end): + """Returns a string that is approximately in the middle of the range. + + (start, end) is treated as a string range, and it is assumed + start <= end in the usual lexicographic string ordering. The output key + mid is guaranteed to satisfy start <= mid <= end. + + The method proceeds by comparing initial characters of start and + end. When the characters are equal, they are appended to the mid + string. In the first place that the characters differ, the + difference characters are averaged and this average is appended to + the mid string. If averaging resulted in rounding down, and + additional character is added to the mid string to make up for the + rounding down. This extra step is necessary for correctness in + the case that the average of the two characters is equal to the + character in the start string. + + This method makes the assumption that most keys are ascii and it + attempts to perform splitting within the ascii range when that + results in a valid split. + + Args: + start: A string. + end: A string such that start <= end. + + Returns: + A string mid such that start <= mid <= end. + """ + if start == end: + return start + start += "\0" + end += "\0" + midpoint = [] + expected_max = 127 + for i in xrange(min(len(start), len(end))): + if start[i] == end[i]: + midpoint.append(start[i]) + else: + ord_sum = ord(start[i]) + ord(end[i]) + midpoint.append(unichr(ord_sum / 2)) + if ord_sum % 2: + if len(start) > i + 1: + ord_start = ord(start[i+1]) + else: + ord_start = 0 + if ord_start < expected_max: + ord_split = (expected_max + ord_start) / 2 + else: + ord_split = (0xFFFF + ord_start) / 2 + midpoint.append(unichr(ord_split)) + break + return "".join(midpoint) + + @staticmethod + def split_keys(key_start, key_end, batch_size): + """Return a key that is between key_start and key_end inclusive. + + This method compares components of the ancestor paths of key_start + and key_end. The first place in the path that differs is + approximately split in half. If the kind components differ, a new + non-existent kind halfway between the two is used to split the + space. If the id_or_name components differ, then a new id_or_name + that is halfway between the two is selected. If the lower + id_or_name is numeric and the upper id_or_name is a string, then + the minumum string key u'\0' is used as the split id_or_name. The + key that is returned is the shared portion of the ancestor path + followed by the generated split component. + + Args: + key_start: A db.Key instance for the lower end of a range. + key_end: A db.Key instance for the upper end of a range. + batch_size: The maximum size of a range that should not be split. + + Returns: + A db.Key instance, k, such that key_start <= k <= key_end. + """ + assert key_start.app() == key_end.app() + path1 = key_start.to_path() + path2 = key_end.to_path() + len1 = len(path1) + len2 = len(path2) + assert len1 % 2 == 0 + assert len2 % 2 == 0 + out_path = [] + min_path_len = min(len1, len2) / 2 + for i in xrange(min_path_len): + kind1 = path1[2*i] + kind2 = path2[2*i] + + if kind1 != kind2: + split_kind = KeyRange.bisect_string_range(kind1, kind2) + out_path.append(split_kind) + out_path.append(unichr(0)) + break + + last = (len1 == len2 == 2*(i + 1)) + + id_or_name1 = path1[2*i + 1] + id_or_name2 = path2[2*i + 1] + id_or_name_split = KeyRange._split_id_or_name( + id_or_name1, id_or_name2, batch_size, last) + if id_or_name1 == id_or_name_split: + out_path.append(kind1) + out_path.append(id_or_name1) + else: + out_path.append(kind1) + out_path.append(id_or_name_split) + break + + return db.Key.from_path(*out_path, **{"_app": key_start.app()}) + + @staticmethod + def _split_id_or_name(id_or_name1, id_or_name2, batch_size, maintain_batches): + """Return an id_or_name that is between id_or_name1 an id_or_name2. + + Attempts to split the range [id_or_name1, id_or_name2] in half, + unless maintain_batches is true and the size of the range + [id_or_name1, id_or_name2] is less than or equal to batch_size. + + Args: + id_or_name1: A number or string or the id_or_name component of a key + id_or_name2: A number or string or the id_or_name component of a key + batch_size: The range size that will not be split if maintain_batches + is true. + maintain_batches: A boolean for whether to keep small ranges intact. + + Returns: + An id_or_name such that id_or_name1 <= id_or_name <= id_or_name2. + """ + if (isinstance(id_or_name1, (int, long)) and + isinstance(id_or_name2, (int, long))): + if not maintain_batches or id_or_name2 - id_or_name1 > batch_size: + return (id_or_name1 + id_or_name2) / 2 + else: + return id_or_name1 + elif (isinstance(id_or_name1, basestring) and + isinstance(id_or_name2, basestring)): + return KeyRange.bisect_string_range(id_or_name1, id_or_name2) + else: + assert (isinstance(id_or_name1, (int, long)) and + isinstance(id_or_name2, basestring)) + return unichr(0) + + @staticmethod + def guess_end_key(kind, + key_start, + probe_count=10, + split_rate=5): + """Guess the end of a key range with a binary search of probe queries. + + When the 'key_start' parameter has a key hierarchy, this function will + only determine the key range for keys in a similar hierarchy. That means + if the keys are in the form: + + kind=Foo, name=bar/kind=Stuff, name=meep + + only this range will be probed: + + kind=Foo, name=*/kind=Stuff, name=* + + That means other entities of kind 'Stuff' that are children of another + parent entity kind will be skipped: + + kind=Other, name=cookie/kind=Stuff, name=meep + + Args: + key_start: The starting key of the search range. In most cases this + should be id = 0 or name = '\0'. + kind: String name of the entity kind. + probe_count: Optional, how many probe queries to run. + split_rate: Exponential rate to use for splitting the range on the + way down from the full key space. For smaller ranges this should + be higher so more of the keyspace is skipped on initial descent. + + Returns: + datastore.Key that is guaranteed to be as high or higher than the + highest key existing for this Kind. Doing a query between 'key_start' and + this returned Key (inclusive) will contain all entities of this Kind. + """ + app = key_start.app() + full_path = key_start.to_path() + for index, piece in enumerate(full_path): + if index % 2 == 0: + continue + elif isinstance(piece, basestring): + full_path[index] = u"\xffff" + else: + full_path[index] = 2**32 + + key_end = datastore.Key.from_path(*full_path, **{"_app": app}) + split_key = key_end + + for i in xrange(probe_count): + for j in xrange(split_rate): + split_key = KeyRange.split_keys(key_start, split_key, 1) + results = datastore.Query( + kind, + {"__key__ >": split_key}, + _app=app, + keys_only=True).Get(1) + if results: + split_rate = 1 + key_start = split_key + split_key = key_end + else: + key_end = split_key + + return key_end + + def to_json(self): + """Serialize KeyRange to json. + + Returns: + string with KeyRange json representation. + """ + if simplejson is None: + raise SimplejsonUnavailableError( + "JSON functionality requires simplejson to be available") + + def key_to_str(key): + if key: + return str(key) + else: + return None + + obj_dict = { + "direction": self.direction, + "key_start": key_to_str(self.key_start), + "key_end": key_to_str(self.key_end), + "include_start": self.include_start, + "include_end": self.include_end, + } + if self._app: + obj_dict["_app"] = self._app + + return simplejson.dumps(obj_dict, sort_keys=True) + + + @staticmethod + def from_json(json_str): + """Deserialize KeyRange from its json representation. + + Args: + json_str: string with json representation created by key_range_to_json. + + Returns: + deserialized KeyRange instance. + """ + if simplejson is None: + raise SimplejsonUnavailableError( + "JSON functionality requires simplejson to be available") + + def key_from_str(key_str): + if key_str: + return db.Key(key_str) + else: + return None + + json = simplejson.loads(json_str) + return KeyRange(key_from_str(json["key_start"]), + key_from_str(json["key_end"]), + json["direction"], + json["include_start"], + json["include_end"], + _app=json.get("_app")) diff --git a/google_appengine/google/appengine/ext/key_range/__init__.pyc b/google_appengine/google/appengine/ext/key_range/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de6f1854dc35a11a8a600428c6a2726696fc0611 GIT binary patch literal 20212 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 bmw7)Z-k)pMmi-#6D^iWn0qeE%b^QGw?_m~; literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/preload/__init__.py b/google_appengine/google/appengine/ext/preload/__init__.py new file mode 100755 index 0000000..ba1cee9 --- /dev/null +++ b/google_appengine/google/appengine/ext/preload/__init__.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Preloads many modules to reduce loading time of third-party code.""" + + + + +import os +_original_os_urandom = os.urandom +def os_urandom_replacement(n): + raise NotImplementedError +os.urandom = os_urandom_replacement +import random +os.urandom = _original_os_urandom +random._urandom = _original_os_urandom + +import BaseHTTPServer +import Bastion +import CGIHTTPServer +import ConfigParser +import Cookie +import DocXMLRPCServer +import HTMLParser +import MimeWriter +import Queue +import SimpleHTTPServer +import SimpleXMLRPCServer +import SocketServer +import StringIO +import UserDict +import UserList +import UserString +import aifc +import anydbm +import atexit +import audiodev +import base64 +import bdb +import binhex +import bisect +import bz2 +import calendar +import cgi +import cgitb +import chunk +import cmd +import code +import codecs +import codeop +import colorsys +import commands +import cookielib +import copy +import copy_reg +import csv +import datetime +import difflib +import dircache +import dis +import doctest +import dumbdbm +import filecmp +import fileinput +import fnmatch +import formatter +import fpformat +import ftplib +import getopt +import getpass +import gettext +import glob +import gzip +import heapq +import hmac +import htmlentitydefs +import htmllib +import httplib +import imaplib +import imghdr +import imputil +import inspect +import keyword +import linecache +import locale +import logging +import macpath +import macurl2path +import mailbox +import mailcap +import markupbase +import math +import md5 +import mhlib +import mimetools +import mimetypes +import modulefinder +import multifile +import mutex +import netrc +import new +import nntplib +import ntpath +import nturl2path +import opcode +import optparse +import os2emxpath +import pdb +import pickle +import pickletools +import pipes +import pkgutil +import popen2 +import poplib +import posixpath +import pprint +import profile +import pstats +import pyclbr +import pydoc +import quopri +import re +import repr +import rfc822 +import robotparser +import sched +import sets +import sgmllib +import sha +import shelve +import shlex +import shutil +import site +import smtplib +import sndhdr +import socket +import stat +import statvfs +import string +import stringold +import stringprep +import struct +import sunau +import sunaudio +import symbol +import sys +import tabnanny +import tarfile +import telnetlib +import tempfile +import textwrap +import time +import timeit +import toaiff +import token +import tokenize +import trace +import traceback +import types +import unittest +import urllib +import urllib2 +import urlparse +import uu +import uuid +import warnings +import wave +import weakref +import whichdb +import xdrlib +import xml.parsers.expat +import xml.dom +import xml.sax +import xmlrpclib +import zipfile +import zlib + +import neo_cs +import neo_util +import webob +import wsgiref.handlers + +from google.appengine.api import datastore +from google.appengine.api import images +from google.appengine.api import mail +from google.appengine.api import memcache +from google.appengine.api import urlfetch +from google.appengine.api import users + +from google.appengine.ext import bulkload +from google.appengine.ext import db +from google.appengine.ext import gql +from google.appengine.ext import search +from google.appengine.ext import webapp + +from google.appengine.runtime import apiproxy + +if __name__ == '__main__': + pass diff --git a/google_appengine/google/appengine/ext/remote_api/__init__.py b/google_appengine/google/appengine/ext/remote_api/__init__.py new file mode 100644 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/ext/remote_api/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/ext/remote_api/__init__.pyc b/google_appengine/google/appengine/ext/remote_api/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db1a8f4a629d9b6562d312d0bc50c96759a0afe4 GIT binary patch literal 169 zcwW2siI?k%Oo~r30~9a%knvjB+{28Lh_kcgiKNDhrC4gwPT8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg|OViGERPZhi?!Ri=J?d}dx|NqoFs XL1hUC&{&(?{FKt1R6CI6#URT7Ho+>p literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/remote_api/handler.py b/google_appengine/google/appengine/ext/remote_api/handler.py new file mode 100755 index 0000000..449352c --- /dev/null +++ b/google_appengine/google/appengine/ext/remote_api/handler.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A handler that exports various App Engine services over HTTP. + +You can export this handler in your app by adding it directly to app.yaml's +list of handlers: + + handlers: + - url: /remote_api + script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py + login: admin + +Then, you can use remote_api_stub to remotely access services exported by this +handler. See the documentation in remote_api_stub.py for details on how to do +this. + +Using this handler without specifying "login: admin" would be extremely unwise. +So unwise that the default handler insists on checking for itself. +""" + + + + + +import google +import logging +import os +import pickle +import sha +import sys +import wsgiref.handlers +import yaml + +from google.appengine.api import api_base_pb +from google.appengine.api import apiproxy_stub +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import users +from google.appengine.datastore import datastore_pb +from google.appengine.ext import webapp +from google.appengine.ext.remote_api import remote_api_pb +from google.appengine.ext.remote_api import remote_api_services +from google.appengine.runtime import apiproxy_errors + + +class RemoteDatastoreStub(apiproxy_stub.APIProxyStub): + """Provides a stub that permits execution of stateful datastore queries. + + Some operations aren't possible using the standard interface. Notably, + datastore RunQuery operations internally store a cursor that is referenced in + later Next calls, and cleaned up at the end of each request. Because every + call to ApiCallHandler takes place in its own request, this isn't possible. + + To work around this, RemoteDatastoreStub provides its own implementation of + RunQuery that immediately returns the query results. + """ + + def __init__(self, service='datastore_v3', _test_stub_map=None): + """Constructor. + + Args: + service: The name of the service + _test_stub_map: An APIProxyStubMap to use for testing purposes. + """ + super(RemoteDatastoreStub, self).__init__(service) + if _test_stub_map: + self.__call = _test_stub_map.MakeSyncCall + else: + self.__call = apiproxy_stub_map.MakeSyncCall + + def _Dynamic_RunQuery(self, request, response): + """Handle a RunQuery request. + + We handle RunQuery by executing a Query and a Next and returning the result + of the Next request. + + This method is DEPRECATED, but left in place for older clients. + """ + runquery_response = datastore_pb.QueryResult() + self.__call('datastore_v3', 'RunQuery', request, runquery_response) + if runquery_response.result_size() > 0: + response.CopyFrom(runquery_response) + return + + next_request = datastore_pb.NextRequest() + next_request.mutable_cursor().CopyFrom(runquery_response.cursor()) + next_request.set_count(request.limit()) + self.__call('datastore_v3', 'Next', next_request, response) + + def _Dynamic_Transaction(self, request, response): + """Handle a Transaction request. + + We handle transactions by accumulating Put requests on the client end, as + well as recording the key and hash of Get requests. When Commit is called, + Transaction is invoked, which verifies that all the entities in the + precondition list still exist and their hashes match, then performs a + transaction of its own to make the updates. + """ + begin_request = datastore_pb.BeginTransactionRequest() + begin_request.set_app(os.environ['APPLICATION_ID']) + tx = datastore_pb.Transaction() + self.__call('datastore_v3', 'BeginTransaction', begin_request, tx) + + preconditions = request.precondition_list() + if preconditions: + get_request = datastore_pb.GetRequest() + get_request.mutable_transaction().CopyFrom(tx) + for precondition in preconditions: + key = get_request.add_key() + key.CopyFrom(precondition.key()) + get_response = datastore_pb.GetResponse() + self.__call('datastore_v3', 'Get', get_request, get_response) + entities = get_response.entity_list() + assert len(entities) == request.precondition_size() + for precondition, entity in zip(preconditions, entities): + if precondition.has_hash() != entity.has_entity(): + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.CONCURRENT_TRANSACTION, + "Transaction precondition failed.") + elif entity.has_entity(): + entity_hash = sha.new(entity.entity().Encode()).digest() + if precondition.hash() != entity_hash: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.CONCURRENT_TRANSACTION, + "Transaction precondition failed.") + + if request.has_puts(): + put_request = request.puts() + put_request.mutable_transaction().CopyFrom(tx) + self.__call('datastore_v3', 'Put', put_request, response) + + if request.has_deletes(): + delete_request = request.deletes() + delete_request.mutable_transaction().CopyFrom(tx) + self.__call('datastore_v3', 'Delete', delete_request, + api_base_pb.VoidProto()) + + self.__call('datastore_v3', 'Commit', tx, api_base_pb.VoidProto()) + + def _Dynamic_GetIDs(self, request, response): + """Fetch unique IDs for a set of paths.""" + for entity in request.entity_list(): + assert entity.property_size() == 0 + assert entity.raw_property_size() == 0 + assert entity.entity_group().element_size() == 0 + lastpart = entity.key().path().element_list()[-1] + assert lastpart.id() == 0 and not lastpart.has_name() + + begin_request = datastore_pb.BeginTransactionRequest() + begin_request.set_app(os.environ['APPLICATION_ID']) + tx = datastore_pb.Transaction() + self.__call('datastore_v3', 'BeginTransaction', begin_request, tx) + + self.__call('datastore_v3', 'Put', request, response) + + self.__call('datastore_v3', 'Rollback', tx, api_base_pb.VoidProto()) + + +SERVICE_PB_MAP = remote_api_services.SERVICE_PB_MAP + +class ApiCallHandler(webapp.RequestHandler): + """A webapp handler that accepts API calls over HTTP and executes them.""" + + LOCAL_STUBS = { + 'remote_datastore': RemoteDatastoreStub('remote_datastore'), + } + + def CheckIsAdmin(self): + if not users.is_current_user_admin(): + self.response.set_status(401) + self.response.out.write( + "You must be logged in as an administrator to access this.") + self.response.headers['Content-Type'] = 'text/plain' + return False + elif 'X-appcfg-api-version' not in self.request.headers: + self.response.set_status(403) + self.response.out.write("This request did not contain a necessary header") + self.response.headers['Content-Type'] = 'text/plain' + return False + return True + + + def get(self): + """Handle a GET. Just show an info page.""" + if not self.CheckIsAdmin(): + return + + rtok = self.request.get('rtok', '0') + app_info = { + 'app_id': os.environ['APPLICATION_ID'], + 'rtok': rtok + } + + self.response.headers['Content-Type'] = 'text/plain' + self.response.out.write(yaml.dump(app_info)) + + def post(self): + """Handle POST requests by executing the API call.""" + if not self.CheckIsAdmin(): + return + + self.response.headers['Content-Type'] = 'application/octet-stream' + response = remote_api_pb.Response() + try: + request = remote_api_pb.Request() + request.ParseFromString(self.request.body) + response_data = self.ExecuteRequest(request) + response.mutable_response().set_contents(response_data.Encode()) + self.response.set_status(200) + except Exception, e: + logging.exception('Exception while handling %s', request) + self.response.set_status(200) + response.mutable_exception().set_contents(pickle.dumps(e)) + if isinstance(e, apiproxy_errors.ApplicationError): + application_error = response.mutable_application_error() + application_error.set_code(e.application_error) + application_error.set_detail(e.error_detail) + self.response.out.write(response.Encode()) + + def ExecuteRequest(self, request): + """Executes an API invocation and returns the response object.""" + service = request.service_name() + method = request.method() + service_methods = SERVICE_PB_MAP.get(service, {}) + request_class, response_class = service_methods.get(method, (None, None)) + if not request_class: + raise apiproxy_errors.CallNotFoundError() + + request_data = request_class() + request_data.ParseFromString(request.request().contents()) + response_data = response_class() + + if service in self.LOCAL_STUBS: + self.LOCAL_STUBS[service].MakeSyncCall(service, method, request_data, + response_data) + else: + apiproxy_stub_map.MakeSyncCall(service, method, request_data, + response_data) + + return response_data + + def InfoPage(self): + """Renders an information page.""" + return """ + + +App Engine API endpoint. + +

App Engine API endpoint.

+

This is an endpoint for the App Engine remote API interface. +Point your stubs (google.appengine.ext.remote_api.remote_api_stub) here.

+ +""" + + +def main(): + application = webapp.WSGIApplication([('.*', ApiCallHandler)]) + wsgiref.handlers.CGIHandler().run(application) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_pb.py b/google_appengine/google/appengine/ext/remote_api/remote_api_pb.py new file mode 100755 index 0000000..bd6a777 --- /dev/null +++ b/google_appengine/google/appengine/ext/remote_api/remote_api_pb.py @@ -0,0 +1,809 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from google.net.proto import ProtocolBuffer +import array +import dummy_thread as thread + +__pychecker__ = """maxreturns=0 maxbranches=0 no-callinit + unusednames=printElemNumber,debug_strs no-special""" + +from google.net.proto.RawMessage import RawMessage +from google.appengine.datastore.datastore_pb import PutRequest +from google.appengine.datastore.datastore_pb import DeleteRequest +from google.appengine.datastore.entity_pb import Reference +class Request(ProtocolBuffer.ProtocolMessage): + has_service_name_ = 0 + service_name_ = "" + has_method_ = 0 + method_ = "" + has_request_ = 0 + + def __init__(self, contents=None): + self.request_ = RawMessage() + if contents is not None: self.MergeFromString(contents) + + def service_name(self): return self.service_name_ + + def set_service_name(self, x): + self.has_service_name_ = 1 + self.service_name_ = x + + def clear_service_name(self): + if self.has_service_name_: + self.has_service_name_ = 0 + self.service_name_ = "" + + def has_service_name(self): return self.has_service_name_ + + def method(self): return self.method_ + + def set_method(self, x): + self.has_method_ = 1 + self.method_ = x + + def clear_method(self): + if self.has_method_: + self.has_method_ = 0 + self.method_ = "" + + def has_method(self): return self.has_method_ + + def request(self): return self.request_ + + def mutable_request(self): self.has_request_ = 1; return self.request_ + + def clear_request(self):self.has_request_ = 0; self.request_.Clear() + + def has_request(self): return self.has_request_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_service_name()): self.set_service_name(x.service_name()) + if (x.has_method()): self.set_method(x.method()) + if (x.has_request()): self.mutable_request().MergeFrom(x.request()) + + def Equals(self, x): + if x is self: return 1 + if self.has_service_name_ != x.has_service_name_: return 0 + if self.has_service_name_ and self.service_name_ != x.service_name_: return 0 + if self.has_method_ != x.has_method_: return 0 + if self.has_method_ and self.method_ != x.method_: return 0 + if self.has_request_ != x.has_request_: return 0 + if self.has_request_ and self.request_ != x.request_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_service_name_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: service_name not set.') + if (not self.has_method_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: method not set.') + if (not self.has_request_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: request not set.') + elif not self.request_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(len(self.service_name_)) + n += self.lengthString(len(self.method_)) + n += self.lengthString(self.request_.ByteSize()) + return n + 3 + + def Clear(self): + self.clear_service_name() + self.clear_method() + self.clear_request() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putPrefixedString(self.service_name_) + out.putVarInt32(26) + out.putPrefixedString(self.method_) + out.putVarInt32(34) + out.putVarInt32(self.request_.ByteSize()) + self.request_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 18: + self.set_service_name(d.getPrefixedString()) + continue + if tt == 26: + self.set_method(d.getPrefixedString()) + continue + if tt == 34: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_request().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_service_name_: res+=prefix+("service_name: %s\n" % self.DebugFormatString(self.service_name_)) + if self.has_method_: res+=prefix+("method: %s\n" % self.DebugFormatString(self.method_)) + if self.has_request_: + res+=prefix+"request <\n" + res+=self.request_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kservice_name = 2 + kmethod = 3 + krequest = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 2: "service_name", + 3: "method", + 4: "request", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class ApplicationError(ProtocolBuffer.ProtocolMessage): + has_code_ = 0 + code_ = 0 + has_detail_ = 0 + detail_ = "" + + def __init__(self, contents=None): + if contents is not None: self.MergeFromString(contents) + + def code(self): return self.code_ + + def set_code(self, x): + self.has_code_ = 1 + self.code_ = x + + def clear_code(self): + if self.has_code_: + self.has_code_ = 0 + self.code_ = 0 + + def has_code(self): return self.has_code_ + + def detail(self): return self.detail_ + + def set_detail(self, x): + self.has_detail_ = 1 + self.detail_ = x + + def clear_detail(self): + if self.has_detail_: + self.has_detail_ = 0 + self.detail_ = "" + + def has_detail(self): return self.has_detail_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_code()): self.set_code(x.code()) + if (x.has_detail()): self.set_detail(x.detail()) + + def Equals(self, x): + if x is self: return 1 + if self.has_code_ != x.has_code_: return 0 + if self.has_code_ and self.code_ != x.code_: return 0 + if self.has_detail_ != x.has_detail_: return 0 + if self.has_detail_ and self.detail_ != x.detail_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_code_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: code not set.') + if (not self.has_detail_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: detail not set.') + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthVarInt64(self.code_) + n += self.lengthString(len(self.detail_)) + return n + 2 + + def Clear(self): + self.clear_code() + self.clear_detail() + + def OutputUnchecked(self, out): + out.putVarInt32(8) + out.putVarInt32(self.code_) + out.putVarInt32(18) + out.putPrefixedString(self.detail_) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 8: + self.set_code(d.getVarInt32()) + continue + if tt == 18: + self.set_detail(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_code_: res+=prefix+("code: %s\n" % self.DebugFormatInt32(self.code_)) + if self.has_detail_: res+=prefix+("detail: %s\n" % self.DebugFormatString(self.detail_)) + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kcode = 1 + kdetail = 2 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "code", + 2: "detail", + }, 2) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.NUMERIC, + 2: ProtocolBuffer.Encoder.STRING, + }, 2, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class Response(ProtocolBuffer.ProtocolMessage): + has_response_ = 0 + response_ = None + has_exception_ = 0 + exception_ = None + has_application_error_ = 0 + application_error_ = None + has_java_exception_ = 0 + java_exception_ = None + + def __init__(self, contents=None): + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def response(self): + if self.response_ is None: + self.lazy_init_lock_.acquire() + try: + if self.response_ is None: self.response_ = RawMessage() + finally: + self.lazy_init_lock_.release() + return self.response_ + + def mutable_response(self): self.has_response_ = 1; return self.response() + + def clear_response(self): + if self.has_response_: + self.has_response_ = 0; + if self.response_ is not None: self.response_.Clear() + + def has_response(self): return self.has_response_ + + def exception(self): + if self.exception_ is None: + self.lazy_init_lock_.acquire() + try: + if self.exception_ is None: self.exception_ = RawMessage() + finally: + self.lazy_init_lock_.release() + return self.exception_ + + def mutable_exception(self): self.has_exception_ = 1; return self.exception() + + def clear_exception(self): + if self.has_exception_: + self.has_exception_ = 0; + if self.exception_ is not None: self.exception_.Clear() + + def has_exception(self): return self.has_exception_ + + def application_error(self): + if self.application_error_ is None: + self.lazy_init_lock_.acquire() + try: + if self.application_error_ is None: self.application_error_ = ApplicationError() + finally: + self.lazy_init_lock_.release() + return self.application_error_ + + def mutable_application_error(self): self.has_application_error_ = 1; return self.application_error() + + def clear_application_error(self): + if self.has_application_error_: + self.has_application_error_ = 0; + if self.application_error_ is not None: self.application_error_.Clear() + + def has_application_error(self): return self.has_application_error_ + + def java_exception(self): + if self.java_exception_ is None: + self.lazy_init_lock_.acquire() + try: + if self.java_exception_ is None: self.java_exception_ = RawMessage() + finally: + self.lazy_init_lock_.release() + return self.java_exception_ + + def mutable_java_exception(self): self.has_java_exception_ = 1; return self.java_exception() + + def clear_java_exception(self): + if self.has_java_exception_: + self.has_java_exception_ = 0; + if self.java_exception_ is not None: self.java_exception_.Clear() + + def has_java_exception(self): return self.has_java_exception_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_response()): self.mutable_response().MergeFrom(x.response()) + if (x.has_exception()): self.mutable_exception().MergeFrom(x.exception()) + if (x.has_application_error()): self.mutable_application_error().MergeFrom(x.application_error()) + if (x.has_java_exception()): self.mutable_java_exception().MergeFrom(x.java_exception()) + + def Equals(self, x): + if x is self: return 1 + if self.has_response_ != x.has_response_: return 0 + if self.has_response_ and self.response_ != x.response_: return 0 + if self.has_exception_ != x.has_exception_: return 0 + if self.has_exception_ and self.exception_ != x.exception_: return 0 + if self.has_application_error_ != x.has_application_error_: return 0 + if self.has_application_error_ and self.application_error_ != x.application_error_: return 0 + if self.has_java_exception_ != x.has_java_exception_: return 0 + if self.has_java_exception_ and self.java_exception_ != x.java_exception_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (self.has_response_ and not self.response_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_exception_ and not self.exception_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_application_error_ and not self.application_error_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_java_exception_ and not self.java_exception_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + if (self.has_response_): n += 1 + self.lengthString(self.response_.ByteSize()) + if (self.has_exception_): n += 1 + self.lengthString(self.exception_.ByteSize()) + if (self.has_application_error_): n += 1 + self.lengthString(self.application_error_.ByteSize()) + if (self.has_java_exception_): n += 1 + self.lengthString(self.java_exception_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_response() + self.clear_exception() + self.clear_application_error() + self.clear_java_exception() + + def OutputUnchecked(self, out): + if (self.has_response_): + out.putVarInt32(10) + out.putVarInt32(self.response_.ByteSize()) + self.response_.OutputUnchecked(out) + if (self.has_exception_): + out.putVarInt32(18) + out.putVarInt32(self.exception_.ByteSize()) + self.exception_.OutputUnchecked(out) + if (self.has_application_error_): + out.putVarInt32(26) + out.putVarInt32(self.application_error_.ByteSize()) + self.application_error_.OutputUnchecked(out) + if (self.has_java_exception_): + out.putVarInt32(34) + out.putVarInt32(self.java_exception_.ByteSize()) + self.java_exception_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 10: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_response().TryMerge(tmp) + continue + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_exception().TryMerge(tmp) + continue + if tt == 26: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_application_error().TryMerge(tmp) + continue + if tt == 34: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_java_exception().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_response_: + res+=prefix+"response <\n" + res+=self.response_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_exception_: + res+=prefix+"exception <\n" + res+=self.exception_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_application_error_: + res+=prefix+"application_error <\n" + res+=self.application_error_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_java_exception_: + res+=prefix+"java_exception <\n" + res+=self.java_exception_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kresponse = 1 + kexception = 2 + kapplication_error = 3 + kjava_exception = 4 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "response", + 2: "exception", + 3: "application_error", + 4: "java_exception", + }, 4) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STRING, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + }, 4, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" +class TransactionRequest_Precondition(ProtocolBuffer.ProtocolMessage): + has_key_ = 0 + has_hash_ = 0 + hash_ = "" + + def __init__(self, contents=None): + self.key_ = Reference() + if contents is not None: self.MergeFromString(contents) + + def key(self): return self.key_ + + def mutable_key(self): self.has_key_ = 1; return self.key_ + + def clear_key(self):self.has_key_ = 0; self.key_.Clear() + + def has_key(self): return self.has_key_ + + def hash(self): return self.hash_ + + def set_hash(self, x): + self.has_hash_ = 1 + self.hash_ = x + + def clear_hash(self): + if self.has_hash_: + self.has_hash_ = 0 + self.hash_ = "" + + def has_hash(self): return self.has_hash_ + + + def MergeFrom(self, x): + assert x is not self + if (x.has_key()): self.mutable_key().MergeFrom(x.key()) + if (x.has_hash()): self.set_hash(x.hash()) + + def Equals(self, x): + if x is self: return 1 + if self.has_key_ != x.has_key_: return 0 + if self.has_key_ and self.key_ != x.key_: return 0 + if self.has_hash_ != x.has_hash_: return 0 + if self.has_hash_ and self.hash_ != x.hash_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + if (not self.has_key_): + initialized = 0 + if debug_strs is not None: + debug_strs.append('Required field: key not set.') + elif not self.key_.IsInitialized(debug_strs): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += self.lengthString(self.key_.ByteSize()) + if (self.has_hash_): n += 1 + self.lengthString(len(self.hash_)) + return n + 1 + + def Clear(self): + self.clear_key() + self.clear_hash() + + def OutputUnchecked(self, out): + out.putVarInt32(18) + out.putVarInt32(self.key_.ByteSize()) + self.key_.OutputUnchecked(out) + if (self.has_hash_): + out.putVarInt32(26) + out.putPrefixedString(self.hash_) + + def TryMerge(self, d): + while 1: + tt = d.getVarInt32() + if tt == 12: break + if tt == 18: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_key().TryMerge(tmp) + continue + if tt == 26: + self.set_hash(d.getPrefixedString()) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + if self.has_key_: + res+=prefix+"key <\n" + res+=self.key_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_hash_: res+=prefix+("hash: %s\n" % self.DebugFormatString(self.hash_)) + return res + +class TransactionRequest(ProtocolBuffer.ProtocolMessage): + has_puts_ = 0 + puts_ = None + has_deletes_ = 0 + deletes_ = None + + def __init__(self, contents=None): + self.precondition_ = [] + self.lazy_init_lock_ = thread.allocate_lock() + if contents is not None: self.MergeFromString(contents) + + def precondition_size(self): return len(self.precondition_) + def precondition_list(self): return self.precondition_ + + def precondition(self, i): + return self.precondition_[i] + + def mutable_precondition(self, i): + return self.precondition_[i] + + def add_precondition(self): + x = TransactionRequest_Precondition() + self.precondition_.append(x) + return x + + def clear_precondition(self): + self.precondition_ = [] + def puts(self): + if self.puts_ is None: + self.lazy_init_lock_.acquire() + try: + if self.puts_ is None: self.puts_ = PutRequest() + finally: + self.lazy_init_lock_.release() + return self.puts_ + + def mutable_puts(self): self.has_puts_ = 1; return self.puts() + + def clear_puts(self): + if self.has_puts_: + self.has_puts_ = 0; + if self.puts_ is not None: self.puts_.Clear() + + def has_puts(self): return self.has_puts_ + + def deletes(self): + if self.deletes_ is None: + self.lazy_init_lock_.acquire() + try: + if self.deletes_ is None: self.deletes_ = DeleteRequest() + finally: + self.lazy_init_lock_.release() + return self.deletes_ + + def mutable_deletes(self): self.has_deletes_ = 1; return self.deletes() + + def clear_deletes(self): + if self.has_deletes_: + self.has_deletes_ = 0; + if self.deletes_ is not None: self.deletes_.Clear() + + def has_deletes(self): return self.has_deletes_ + + + def MergeFrom(self, x): + assert x is not self + for i in xrange(x.precondition_size()): self.add_precondition().CopyFrom(x.precondition(i)) + if (x.has_puts()): self.mutable_puts().MergeFrom(x.puts()) + if (x.has_deletes()): self.mutable_deletes().MergeFrom(x.deletes()) + + def Equals(self, x): + if x is self: return 1 + if len(self.precondition_) != len(x.precondition_): return 0 + for e1, e2 in zip(self.precondition_, x.precondition_): + if e1 != e2: return 0 + if self.has_puts_ != x.has_puts_: return 0 + if self.has_puts_ and self.puts_ != x.puts_: return 0 + if self.has_deletes_ != x.has_deletes_: return 0 + if self.has_deletes_ and self.deletes_ != x.deletes_: return 0 + return 1 + + def IsInitialized(self, debug_strs=None): + initialized = 1 + for p in self.precondition_: + if not p.IsInitialized(debug_strs): initialized=0 + if (self.has_puts_ and not self.puts_.IsInitialized(debug_strs)): initialized = 0 + if (self.has_deletes_ and not self.deletes_.IsInitialized(debug_strs)): initialized = 0 + return initialized + + def ByteSize(self): + n = 0 + n += 2 * len(self.precondition_) + for i in xrange(len(self.precondition_)): n += self.precondition_[i].ByteSize() + if (self.has_puts_): n += 1 + self.lengthString(self.puts_.ByteSize()) + if (self.has_deletes_): n += 1 + self.lengthString(self.deletes_.ByteSize()) + return n + 0 + + def Clear(self): + self.clear_precondition() + self.clear_puts() + self.clear_deletes() + + def OutputUnchecked(self, out): + for i in xrange(len(self.precondition_)): + out.putVarInt32(11) + self.precondition_[i].OutputUnchecked(out) + out.putVarInt32(12) + if (self.has_puts_): + out.putVarInt32(34) + out.putVarInt32(self.puts_.ByteSize()) + self.puts_.OutputUnchecked(out) + if (self.has_deletes_): + out.putVarInt32(42) + out.putVarInt32(self.deletes_.ByteSize()) + self.deletes_.OutputUnchecked(out) + + def TryMerge(self, d): + while d.avail() > 0: + tt = d.getVarInt32() + if tt == 11: + self.add_precondition().TryMerge(d) + continue + if tt == 34: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_puts().TryMerge(tmp) + continue + if tt == 42: + length = d.getVarInt32() + tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length) + d.skip(length) + self.mutable_deletes().TryMerge(tmp) + continue + if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + d.skipData(tt) + + + def __str__(self, prefix="", printElemNumber=0): + res="" + cnt=0 + for e in self.precondition_: + elm="" + if printElemNumber: elm="(%d)" % cnt + res+=prefix+("Precondition%s {\n" % elm) + res+=e.__str__(prefix + " ", printElemNumber) + res+=prefix+"}\n" + cnt+=1 + if self.has_puts_: + res+=prefix+"puts <\n" + res+=self.puts_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + if self.has_deletes_: + res+=prefix+"deletes <\n" + res+=self.deletes_.__str__(prefix + " ", printElemNumber) + res+=prefix+">\n" + return res + + + def _BuildTagLookupTable(sparse, maxtag, default=None): + return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)]) + + kPreconditionGroup = 1 + kPreconditionkey = 2 + kPreconditionhash = 3 + kputs = 4 + kdeletes = 5 + + _TEXT = _BuildTagLookupTable({ + 0: "ErrorCode", + 1: "Precondition", + 2: "key", + 3: "hash", + 4: "puts", + 5: "deletes", + }, 5) + + _TYPES = _BuildTagLookupTable({ + 0: ProtocolBuffer.Encoder.NUMERIC, + 1: ProtocolBuffer.Encoder.STARTGROUP, + 2: ProtocolBuffer.Encoder.STRING, + 3: ProtocolBuffer.Encoder.STRING, + 4: ProtocolBuffer.Encoder.STRING, + 5: ProtocolBuffer.Encoder.STRING, + }, 5, ProtocolBuffer.Encoder.MAX_TYPE) + + _STYLE = """""" + _STYLE_CONTENT_TYPE = """""" + +__all__ = ['Request','ApplicationError','Response','TransactionRequest','TransactionRequest_Precondition'] diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_pb.pyc b/google_appengine/google/appengine/ext/remote_api/remote_api_pb.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45adfac61f977741bb1514f6ed6d38f5ab41ea99 GIT binary patch literal 37514 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 NskNyePL-$L_#ePN&JO?p literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_services.py b/google_appengine/google/appengine/ext/remote_api/remote_api_services.py new file mode 100755 index 0000000..a5234e6 --- /dev/null +++ b/google_appengine/google/appengine/ext/remote_api/remote_api_services.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Service configuration for remote API. + +This module is shared by both the remote_api_stub and the handler. +""" + +import sys + +from google.appengine.api import api_base_pb +from google.appengine.api import mail_service_pb +from google.appengine.api import urlfetch_service_pb +from google.appengine.api.blobstore import blobstore_service_pb +from google.appengine.api.capabilities import capability_service_pb +from google.appengine.api.images import images_service_pb +from google.appengine.api.memcache import memcache_service_pb +try: + __import__('google.appengine.api.labs.taskqueue.taskqueue_service_pb') + taskqueue_service_pb = sys.modules.get( + 'google.appengine.api.labs.taskqueue.taskqueue_service_pb') +except ImportError: + from google.appengine.api.taskqueue import taskqueue_service_pb +from google.appengine.api.xmpp import xmpp_service_pb +from google.appengine.datastore import datastore_pb +from google.appengine.ext.remote_api import remote_api_pb + + +SERVICE_PB_MAP = { + 'blobstore': { + 'CreateUploadURL': (blobstore_service_pb.CreateUploadURLRequest, + blobstore_service_pb.CreateUploadURLResponse), + 'DeleteBlob': (blobstore_service_pb.DeleteBlobRequest, + api_base_pb.VoidProto), + 'FetchData': (blobstore_service_pb.FetchDataRequest, + blobstore_service_pb.FetchDataResponse), + }, + 'capability_service': { + 'IsEnabled': (capability_service_pb.IsEnabledRequest, + capability_service_pb.IsEnabledResponse), + }, + 'datastore_v3': { + 'Get': (datastore_pb.GetRequest, datastore_pb.GetResponse), + 'Put': (datastore_pb.PutRequest, datastore_pb.PutResponse), + 'Delete': (datastore_pb.DeleteRequest, datastore_pb.DeleteResponse), + 'Count': (datastore_pb.Query, api_base_pb.Integer64Proto), + 'GetIndices': (api_base_pb.StringProto, datastore_pb.CompositeIndices), + 'AllocateIds':(datastore_pb.AllocateIdsRequest, + datastore_pb.AllocateIdsResponse), + 'GetSchema': (datastore_pb.GetSchemaRequest, datastore_pb.Schema), + 'RunQuery': (datastore_pb.Query, + datastore_pb.QueryResult), + 'RunCompiledQuery':(datastore_pb.RunCompiledQueryRequest, + datastore_pb.QueryResult), + }, + 'images': { + 'Transform': (images_service_pb.ImagesTransformRequest, + images_service_pb.ImagesTransformResponse), + 'Composite': (images_service_pb.ImagesCompositeRequest, + images_service_pb.ImagesCompositeResponse), + 'Histogram': (images_service_pb.ImagesHistogramRequest, + images_service_pb.ImagesHistogramResponse), + }, + 'mail': { + 'Send': (mail_service_pb.MailMessage, api_base_pb.VoidProto), + 'SendToAdmins': (mail_service_pb.MailMessage, api_base_pb.VoidProto), + }, + 'memcache': { + 'Get': (memcache_service_pb.MemcacheGetRequest, + memcache_service_pb.MemcacheGetResponse), + 'Set': (memcache_service_pb.MemcacheSetRequest, + memcache_service_pb.MemcacheSetResponse), + 'Delete': (memcache_service_pb.MemcacheDeleteRequest, + memcache_service_pb.MemcacheDeleteResponse), + 'Increment': (memcache_service_pb.MemcacheIncrementRequest, + memcache_service_pb.MemcacheIncrementResponse), + 'FlushAll': (memcache_service_pb.MemcacheFlushRequest, + memcache_service_pb.MemcacheFlushResponse), + 'Stats': (memcache_service_pb.MemcacheStatsRequest, + memcache_service_pb.MemcacheStatsResponse), + }, + 'remote_datastore': { + 'RunQuery': (datastore_pb.Query, datastore_pb.QueryResult), + 'Transaction': (remote_api_pb.TransactionRequest, + datastore_pb.PutResponse), + 'GetIDs': (remote_api_pb.PutRequest, datastore_pb.PutResponse), + }, + 'taskqueue': { + 'Add': (taskqueue_service_pb.TaskQueueAddRequest, + taskqueue_service_pb.TaskQueueAddResponse), + 'BulkAdd': (taskqueue_service_pb.TaskQueueBulkAddRequest, + taskqueue_service_pb.TaskQueueBulkAddResponse), + 'UpdateQueue':(taskqueue_service_pb.TaskQueueUpdateQueueRequest, + taskqueue_service_pb.TaskQueueUpdateQueueResponse), + 'FetchQueues':(taskqueue_service_pb.TaskQueueFetchQueuesRequest, + taskqueue_service_pb.TaskQueueFetchQueuesResponse), + 'FetchQueueStats':( + taskqueue_service_pb.TaskQueueFetchQueueStatsRequest, + taskqueue_service_pb.TaskQueueFetchQueueStatsResponse), + }, + 'urlfetch': { + 'Fetch': (urlfetch_service_pb.URLFetchRequest, + urlfetch_service_pb.URLFetchResponse), + }, + 'xmpp': { + 'GetPresence': (xmpp_service_pb.PresenceRequest, + xmpp_service_pb.PresenceResponse), + 'SendMessage': (xmpp_service_pb.XmppMessageRequest, + xmpp_service_pb.XmppMessageResponse), + 'SendInvite': (xmpp_service_pb.XmppInviteRequest, + xmpp_service_pb.XmppInviteResponse), + }, +} + diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_services.pyc b/google_appengine/google/appengine/ext/remote_api/remote_api_services.pyc new file mode 100644 index 0000000000000000000000000000000000000000..196122882ab6c90353a5e80014d5df663b7e7743 GIT binary patch literal 4330 zcwT*0>2exJ5bgyX780`V!;&q0%NO{Rk2sEX*(R|Z86?}GDn%{qNQ_x_@y_5_zw=6Y zkvv3RAeH|~PtWc$h$X0KZGZiB&vZ|34}boh&HT0Mysbm{nZfT%{EC0l01iL}r3Fq3 znif7;#VjTE=k`KtThGbM867-0moQ5_CdQ8wo^uL7uDPTjupBRO^jO0_$6(nCp z@(8dj@TVl7#pOZeJA$4QbWKCw6Z9f34=O(p^pc?K8rmdii=cH4Z4UjmjtW znwQs~ski?#iZTnn@;b}aeeZ}LcLG~--^(5OK`vk|U$WfhelefP95lGdwS1@JvK&5% zh8-{`S3AkoeA&p!1`Fv`yUnXYc4|4>a|mzXgUf<^hX0G7ck31|D#uYMX3Z9?+OBEv zEQ*%R-Kx-giVd?UI)Qt{WWCX6oI_D_{hE+|!1}E7DC&0Gu5p*klRon_3f{7hnMho; z#aeZ{-Y~<8Cx{>W{;|vQcDv2IWA3RZo_Fn<$V*!^zjRoKb)OPI`d7}_3OB^q9bS_G?%fy zEnA*Jt<>?}cUW*Dr?j%|x7r+cp~2q#zdoP<2Z8Mg?A8`(%E1?0>MY)Hq#g%$i?*ka zSq_2fdC#G6>fylObXwfg7S(=~R2h?_N_cgwF2iip24A@y(ZC!i$ugEs5FE7$rARxn z>*_V4byWp+1f>+7Q=9~?wCOl<6#s8^+@^X~F%H|vVeeIckWObss-WZT>UtAur*TKo z=7>R?i(VWGgqzt9m|$LA2UZ2xV?tnQ=(v<#^uDSJs&c44T>nJ{S39g$9lu_!(yqmc zpgvA|6H16fNp)u_rF<_8;N|0{E2)t3;vin!fSiev+Lv4zMAfHUQYDb*2a%%lDyj3K zysK4?ofAmRPGzbGouEvyG9`sky^6%z4FZ${)9RkY@*s*TY)PGtJeNd?JETM>yOzXZ z-(}o&CUM54i^W}q>+qP1pE!$$h?T#Lx6)51wN9&HFcI<7vM)59)uTcDm(EgcU!^FaGd z;TF1XaWegGhN;gj9-aMehADJY3GLzFj0TCS*IxLF8HYQ28V=n}dO4Y1Ht6#P-h229 z36{`l$uPwuJ@Q*lHioWqhLz@jT#)Xv_pvN8tSW71K6fpO{oG_5OA#uEsIdYup z!3<(_9h$^0W}MC^9NA%qo2SzQ8}n4*`X-IoGpEpytfo?VxAdX7y<6Sis_t#>uOk9o zA$~;^i0JQhDjmw%_)l48d+a8A)>`brvVC^- MoWMz$t99}J0ISH1f&c&j literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py b/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py new file mode 100755 index 0000000..7332c94 --- /dev/null +++ b/google_appengine/google/appengine/ext/remote_api/remote_api_stub.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""An apiproxy stub that calls a remote handler via HTTP. + +This allows easy remote access to the App Engine datastore, and potentially any +of the other App Engine APIs, using the same interface you use when accessing +the service locally. + +An example Python script: +--- +from google.appengine.ext import db +from google.appengine.ext.remote_api import remote_api_stub +from myapp import models +import getpass + +def auth_func(): + return (raw_input('Username:'), getpass.getpass('Password:')) + +remote_api_stub.ConfigureRemoteDatastore('my-app', '/remote_api', auth_func) + +# Now you can access the remote datastore just as if your code was running on +# App Engine! + +houses = models.House.all().fetch(100) +for a_house in q: + a_house.doors += 1 +db.put(houses) +--- + +A few caveats: +- Where possible, avoid iterating over queries. Fetching as many results as you + will need is faster and more efficient. If you don't know how many results + you need, or you need 'all of them', iterating is fine. +- Likewise, it's a good idea to put entities in batches. Instead of calling put + for each individual entity, accumulate them and put them in batches using + db.put(), if you can. +- Requests and responses are still limited to 1MB each, so if you have large + entities or try and fetch or put many of them at once, your requests may fail. +""" + + + + + +import google +import os +import pickle +import random +import sha +import sys +import thread +import threading +import yaml + +from google.appengine.api import datastore +from google.appengine.api import apiproxy_rpc +from google.appengine.api import apiproxy_stub_map +from google.appengine.datastore import datastore_pb +from google.appengine.ext.remote_api import remote_api_pb +from google.appengine.ext.remote_api import remote_api_services +from google.appengine.runtime import apiproxy_errors +from google.appengine.tools import appengine_rpc + + +class Error(Exception): + """Base class for exceptions in this module.""" + + +class ConfigurationError(Error): + """Exception for configuration errors.""" + + +class UnknownJavaServerError(Error): + """Exception for exceptions returned from a Java remote_api handler.""" + + +def GetUserAgent(): + """Determines the value of the 'User-agent' header to use for HTTP requests. + + Returns: + String containing the 'user-agent' header value, which includes the SDK + version, the platform information, and the version of Python; + e.g., "remote_api/1.0.1 Darwin/9.2.0 Python/2.5.2". + """ + product_tokens = [] + + product_tokens.append("Google-remote_api/1.0") + + product_tokens.append(appengine_rpc.GetPlatformToken()) + + python_version = ".".join(str(i) for i in sys.version_info) + product_tokens.append("Python/%s" % python_version) + + return " ".join(product_tokens) + + +def GetSourceName(): + return "Google-remote_api-1.0" + + +class TransactionData(object): + """Encapsulates data about an individual transaction.""" + + def __init__(self, thread_id): + self.thread_id = thread_id + self.preconditions = {} + self.entities = {} + + +class RemoteStub(object): + """A stub for calling services on a remote server over HTTP. + + You can use this to stub out any service that the remote server supports. + """ + + def __init__(self, server, path, _test_stub_map=None): + """Constructs a new RemoteStub that communicates with the specified server. + + Args: + server: An instance of a subclass of + google.appengine.tools.appengine_rpc.AbstractRpcServer. + path: The path to the handler this stub should send requests to. + """ + self._server = server + self._path = path + self._test_stub_map = _test_stub_map + + def _PreHookHandler(self, service, call, request, response): + pass + + def _PostHookHandler(self, service, call, request, response): + pass + + def MakeSyncCall(self, service, call, request, response): + self._PreHookHandler(service, call, request, response) + try: + test_stub = self._test_stub_map and self._test_stub_map.GetStub(service) + if test_stub: + test_stub.MakeSyncCall(service, call, request, response) + else: + self._MakeRealSyncCall(service, call, request, response) + finally: + self._PostHookHandler(service, call, request, response) + + def _MakeRealSyncCall(self, service, call, request, response): + request_pb = remote_api_pb.Request() + request_pb.set_service_name(service) + request_pb.set_method(call) + request_pb.mutable_request().set_contents(request.Encode()) + + response_pb = remote_api_pb.Response() + encoded_request = request_pb.Encode() + encoded_response = self._server.Send(self._path, encoded_request) + response_pb.ParseFromString(encoded_response) + + if response_pb.has_application_error(): + error_pb = response_pb.application_error() + raise apiproxy_errors.ApplicationError(error_pb.code(), + error_pb.detail()) + elif response_pb.has_exception(): + raise pickle.loads(response_pb.exception().contents()) + elif response_pb.has_java_exception(): + raise UnknownJavaServerError("An unknown error has occured in the " + "Java remote_api handler for this call.") + else: + response.ParseFromString(response_pb.response().contents()) + + def CreateRPC(self): + return apiproxy_rpc.RPC(stub=self) + + +class RemoteDatastoreStub(RemoteStub): + """A specialised stub for accessing the App Engine datastore remotely. + + A specialised stub is required because there are some datastore operations + that preserve state between calls. This stub makes queries possible. + Transactions on the remote datastore are unfortunately still impossible. + """ + + def __init__(self, server, path, default_result_count=20, + _test_stub_map=None): + """Constructor. + + Args: + server: The server name to connect to. + path: The URI path on the server. + default_result_count: The number of items to fetch, by default, in a + datastore Query or Next operation. This affects the batch size of + query iterators. + """ + super(RemoteDatastoreStub, self).__init__(server, path, _test_stub_map) + self.default_result_count = default_result_count + self.__queries = {} + self.__transactions = {} + + self.__next_local_cursor = 1 + self.__local_cursor_lock = threading.Lock() + self.__next_local_tx = 1 + self.__local_tx_lock = threading.Lock() + + def MakeSyncCall(self, service, call, request, response): + assert service == 'datastore_v3' + + explanation = [] + assert request.IsInitialized(explanation), explanation + + handler = getattr(self, '_Dynamic_' + call, None) + if handler: + handler(request, response) + else: + super(RemoteDatastoreStub, self).MakeSyncCall(service, call, request, + response) + + assert response.IsInitialized(explanation), explanation + + def _Dynamic_RunQuery(self, query, query_result, cursor_id = None): + super(RemoteDatastoreStub, self).MakeSyncCall( + 'datastore_v3', 'RunQuery', query, query_result) + + if cursor_id is None: + self.__local_cursor_lock.acquire() + try: + cursor_id = self.__next_local_cursor + self.__next_local_cursor += 1 + finally: + self.__local_cursor_lock.release() + + if query_result.more_results(): + query.set_offset(query.offset() + query_result.result_size()) + if query.has_limit(): + query.set_limit(query.limit() - query_result.result_size()) + self.__queries[cursor_id] = query + else: + self.__queries[cursor_id] = None + + query_result.mutable_cursor().set_cursor(cursor_id) + + def _Dynamic_Next(self, next_request, query_result): + cursor_id = next_request.cursor().cursor() + if cursor_id not in self.__queries: + raise apiproxy_errors.ApplicationError(datastore_pb.Error.BAD_REQUEST, + 'Cursor %d not found' % cursor_id) + query = self.__queries[cursor_id] + + if query is None: + query_result.set_more_results(False) + return + else: + if next_request.has_count(): + query.set_count(next_request.count()) + else: + query.clear_count() + + self._Dynamic_RunQuery(query, query_result, cursor_id) + + def _Dynamic_Get(self, get_request, get_response): + txid = None + if get_request.has_transaction(): + txid = get_request.transaction().handle() + txdata = self.__transactions[txid] + assert (txdata.thread_id == + thread.get_ident()), "Transactions are single-threaded." + + keys = [(k, k.Encode()) for k in get_request.key_list()] + + new_request = datastore_pb.GetRequest() + for key, enckey in keys: + if enckey not in txdata.entities: + new_request.add_key().CopyFrom(key) + else: + new_request = get_request + + if new_request.key_size() > 0: + super(RemoteDatastoreStub, self).MakeSyncCall( + 'datastore_v3', 'Get', new_request, get_response) + + if txid is not None: + newkeys = new_request.key_list() + entities = get_response.entity_list() + for key, entity in zip(newkeys, entities): + entity_hash = None + if entity.has_entity(): + entity_hash = sha.new(entity.entity().Encode()).digest() + txdata.preconditions[key.Encode()] = (key, entity_hash) + + new_response = datastore_pb.GetResponse() + it = iter(get_response.entity_list()) + for key, enckey in keys: + if enckey in txdata.entities: + cached_entity = txdata.entities[enckey][1] + if cached_entity: + new_response.add_entity().mutable_entity().CopyFrom(cached_entity) + else: + new_response.add_entity() + else: + new_entity = it.next() + if new_entity.has_entity(): + assert new_entity.entity().key() == key + new_response.add_entity().CopyFrom(new_entity) + else: + new_response.add_entity() + get_response.CopyFrom(new_response) + + def _Dynamic_Put(self, put_request, put_response): + if put_request.has_transaction(): + entities = put_request.entity_list() + + requires_id = lambda x: x.id() == 0 and not x.has_name() + new_ents = [e for e in entities + if requires_id(e.key().path().element_list()[-1])] + id_request = remote_api_pb.PutRequest() + if new_ents: + for ent in new_ents: + e = id_request.add_entity() + e.mutable_key().CopyFrom(ent.key()) + e.mutable_entity_group() + id_response = datastore_pb.PutResponse() + super(RemoteDatastoreStub, self).MakeSyncCall( + 'remote_datastore', 'GetIDs', id_request, id_response) + assert id_request.entity_size() == id_response.key_size() + for key, ent in zip(id_response.key_list(), new_ents): + ent.mutable_key().CopyFrom(key) + ent.mutable_entity_group().add_element().CopyFrom( + key.path().element(0)) + + txid = put_request.transaction().handle() + txdata = self.__transactions[txid] + assert (txdata.thread_id == + thread.get_ident()), "Transactions are single-threaded." + for entity in entities: + txdata.entities[entity.key().Encode()] = (entity.key(), entity) + put_response.add_key().CopyFrom(entity.key()) + else: + super(RemoteDatastoreStub, self).MakeSyncCall( + 'datastore_v3', 'Put', put_request, put_response) + + def _Dynamic_Delete(self, delete_request, response): + if delete_request.has_transaction(): + txid = delete_request.transaction().handle() + txdata = self.__transactions[txid] + assert (txdata.thread_id == + thread.get_ident()), "Transactions are single-threaded." + for key in delete_request.key_list(): + txdata.entities[key.Encode()] = (key, None) + else: + super(RemoteDatastoreStub, self).MakeSyncCall( + 'datastore_v3', 'Delete', delete_request, response) + + def _Dynamic_BeginTransaction(self, request, transaction): + self.__local_tx_lock.acquire() + try: + txid = self.__next_local_tx + self.__transactions[txid] = TransactionData(thread.get_ident()) + self.__next_local_tx += 1 + finally: + self.__local_tx_lock.release() + transaction.set_handle(txid) + + def _Dynamic_Commit(self, transaction, transaction_response): + txid = transaction.handle() + if txid not in self.__transactions: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Transaction %d not found.' % (txid,)) + + txdata = self.__transactions[txid] + assert (txdata.thread_id == + thread.get_ident()), "Transactions are single-threaded." + del self.__transactions[txid] + + tx = remote_api_pb.TransactionRequest() + for key, hash in txdata.preconditions.values(): + precond = tx.add_precondition() + precond.mutable_key().CopyFrom(key) + if hash: + precond.set_hash(hash) + + puts = tx.mutable_puts() + deletes = tx.mutable_deletes() + for key, entity in txdata.entities.values(): + if entity: + puts.add_entity().CopyFrom(entity) + else: + deletes.add_key().CopyFrom(key) + + super(RemoteDatastoreStub, self).MakeSyncCall( + 'remote_datastore', 'Transaction', + tx, datastore_pb.PutResponse()) + + def _Dynamic_Rollback(self, transaction, transaction_response): + txid = transaction.handle() + self.__local_tx_lock.acquire() + try: + if txid not in self.__transactions: + raise apiproxy_errors.ApplicationError( + datastore_pb.Error.BAD_REQUEST, + 'Transaction %d not found.' % (txid,)) + + txdata = self.__transactions[txid] + assert (txdata[txid].thread_id == + thread.get_ident()), "Transactions are single-threaded." + del self.__transactions[txid] + finally: + self.__local_tx_lock.release() + + def _Dynamic_CreateIndex(self, index, id_response): + raise apiproxy_errors.CapabilityDisabledError( + 'The remote datastore does not support index manipulation.') + + def _Dynamic_UpdateIndex(self, index, void): + raise apiproxy_errors.CapabilityDisabledError( + 'The remote datastore does not support index manipulation.') + + def _Dynamic_DeleteIndex(self, index, void): + raise apiproxy_errors.CapabilityDisabledError( + 'The remote datastore does not support index manipulation.') + + +ALL_SERVICES = set(remote_api_services.SERVICE_PB_MAP) + +def ConfigureRemoteApi(app_id, + path, + auth_func, + servername=None, + rpc_server_factory=appengine_rpc.HttpRpcServer, + rtok=None, + secure=False, + services=None, + default_auth_domain=None, + save_cookies=False): + """Does necessary setup to allow easy remote access to App Engine APIs. + + Either servername must be provided or app_id must not be None. If app_id + is None and a servername is provided, this function will send a request + to the server to retrieve the app_id. + + Args: + app_id: The app_id of your app, as declared in app.yaml. + path: The path to the remote_api handler for your app + (for example, '/remote_api'). + auth_func: A function that takes no arguments and returns a + (username, password) tuple. This will be called if your application + requires authentication to access the remote_api handler (it should!) + and you do not already have a valid auth cookie. + servername: The hostname your app is deployed on. Defaults to + .appspot.com. + rpc_server_factory: A factory to construct the rpc server for the datastore. + rtok: The validation token to sent with app_id lookups. If None, a random + token is used. + secure: Use SSL when communicating with the server. + services: A list of services to set up stubs for. If specified, only those + services are configured; by default all supported services are configured. + default_auth_domain: The authentication domain to use by default. + save_cookies: Forwarded to rpc_server_factory function. + + Raises: + urllib2.HTTPError: if app_id is not provided and there is an error while + retrieving it. + ConfigurationError: if there is a error configuring the DatstoreFileStub. + """ + if not servername and not app_id: + raise ConfigurationError('app_id or servername required') + if not servername: + servername = '%s.appspot.com' % (app_id,) + server = rpc_server_factory(servername, auth_func, GetUserAgent(), + GetSourceName(), save_cookies=save_cookies, + debug_data=False, secure=secure) + if not app_id: + if not rtok: + random.seed() + rtok = str(random.random())[2:] + urlargs = {'rtok': rtok} + response = server.Send(path, payload=None, **urlargs) + if not response.startswith('{'): + raise ConfigurationError( + 'Invalid response recieved from server: %s' % response) + app_info = yaml.load(response) + if not app_info or 'rtok' not in app_info or 'app_id' not in app_info: + raise ConfigurationError('Error parsing app_id lookup response') + if app_info['rtok'] != rtok: + raise ConfigurationError('Token validation failed during app_id lookup. ' + '(sent %s, got %s)' % (repr(rtok), + repr(app_info['rtok']))) + app_id = app_info['app_id'] + + if services is not None: + services = set(services) + unsupported = services.difference(ALL_SERVICES) + if unsupported: + raise ConfigurationError('Unsupported service(s): %s' + % (', '.join(unsupported),)) + else: + services = set(ALL_SERVICES) + + os.environ['APPLICATION_ID'] = app_id + if default_auth_domain: + os.environ['AUTH_DOMAIN'] = default_auth_domain + elif 'AUTH_DOMAIN' not in os.environ: + os.environ['AUTH_DOMAIN'] = 'gmail.com' + apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() + if 'datastore_v3' in services: + services.remove('datastore_v3') + datastore_stub = RemoteDatastoreStub(server, path) + apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore_stub) + stub = RemoteStub(server, path) + for service in services: + apiproxy_stub_map.apiproxy.RegisterStub(service, stub) + + +def MaybeInvokeAuthentication(): + """Sends an empty request through to the configured end-point. + + If authentication is necessary, this will cause the rpc_server to invoke + interactive authentication. + """ + datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3') + if isinstance(datastore_stub, RemoteStub): + datastore_stub._server.Send(datastore_stub._path, payload=None) + else: + raise ConfigurationError('remote_api is not configured.') + + +ConfigureRemoteDatastore = ConfigureRemoteApi diff --git a/google_appengine/google/appengine/ext/remote_api/remote_api_stub.pyc b/google_appengine/google/appengine/ext/remote_api/remote_api_stub.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc6d5ef12832cc350d4234afcfa71478f90bd3cb GIT binary patch literal 21712 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 self.get_time(): + return + + for name, count in self.transferred[throttle_name].items(): + + + self.prior_block[throttle_name][name] = count + self.transferred[throttle_name][name] = 0 + + self.totals[throttle_name][name] += count + + self.last_rotate[throttle_name] = self.get_time() + + finally: + self.rotate_mutex[throttle_name].release() + + def TotalTransferred(self, throttle_name): + """Return the total transferred, and over what period. + + Args: + throttle_name: The name of the throttle to total. + + Returns: + A tuple of the total count and running time for the given throttle name. + """ + total = 0 + for count in self.totals[throttle_name].values(): + total += count + for count in self.transferred[throttle_name].values(): + total += count + return total, self.get_time() - self.start_time + + +BANDWIDTH_UP = 'http-bandwidth-up' +BANDWIDTH_DOWN = 'http-bandwidth-down' +REQUESTS = 'http-requests' +HTTPS_BANDWIDTH_UP = 'https-bandwidth-up' +HTTPS_BANDWIDTH_DOWN = 'https-bandwidth-down' +HTTPS_REQUESTS = 'https-requests' +DATASTORE_CALL_COUNT = 'datastore-call-count' +ENTITIES_FETCHED = 'entities-fetched' +ENTITIES_MODIFIED = 'entities-modified' +INDEX_MODIFICATIONS = 'index-modifications' + + +DEFAULT_LIMITS = { + BANDWIDTH_UP: 100000, + BANDWIDTH_DOWN: 100000, + REQUESTS: 15, + HTTPS_BANDWIDTH_UP: 100000, + HTTPS_BANDWIDTH_DOWN: 100000, + HTTPS_REQUESTS: 15, + DATASTORE_CALL_COUNT: 120, + ENTITIES_FETCHED: 400, + ENTITIES_MODIFIED: 400, + INDEX_MODIFICATIONS: 1600, +} + +NO_LIMITS = { + BANDWIDTH_UP: None, + BANDWIDTH_DOWN: None, + REQUESTS: None, + HTTPS_BANDWIDTH_UP: None, + HTTPS_BANDWIDTH_DOWN: None, + HTTPS_REQUESTS: None, + DATASTORE_CALL_COUNT: None, + ENTITIES_FETCHED: None, + ENTITIES_MODIFIED: None, + INDEX_MODIFICATIONS: None, +} + + +def DefaultThrottle(multiplier=1.0): + """Return a Throttle instance with multiplier * the quota limits.""" + layout = dict([(name, multiplier * limit) + for (name, limit) in DEFAULT_LIMITS.iteritems()]) + return Throttle(layout=layout) + + +class ThrottleHandler(urllib2.BaseHandler): + """A urllib2 handler for http and https requests that adds to a throttle.""" + + def __init__(self, throttle): + """Initialize a ThrottleHandler. + + Args: + throttle: A Throttle instance to call for bandwidth and http/https request + throttling. + """ + self.throttle = throttle + + def AddRequest(self, throttle_name, req): + """Add to bandwidth throttle for given request. + + Args: + throttle_name: The name of the bandwidth throttle to add to. + req: The request whose size will be added to the throttle. + """ + size = 0 + for key, value in req.headers.iteritems(): + size += len('%s: %s\n' % (key, value)) + for key, value in req.unredirected_hdrs.iteritems(): + size += len('%s: %s\n' % (key, value)) + (unused_scheme, + unused_host_port, url_path, + unused_query, unused_fragment) = urlparse.urlsplit(req.get_full_url()) + size += len('%s %s HTTP/1.1\n' % (req.get_method(), url_path)) + data = req.get_data() + if data: + size += len(data) + self.throttle.AddTransfer(throttle_name, size) + + def AddResponse(self, throttle_name, res): + """Add to bandwidth throttle for given response. + + Args: + throttle_name: The name of the bandwidth throttle to add to. + res: The response whose size will be added to the throttle. + """ + content = res.read() + + def ReturnContent(): + return content + + res.read = ReturnContent + size = len(content) + headers = res.info() + for key, value in headers.items(): + size += len('%s: %s\n' % (key, value)) + self.throttle.AddTransfer(throttle_name, size) + + def http_request(self, req): + """Process an HTTP request. + + If the throttle is over quota, sleep first. Then add request size to + throttle before returning it to be sent. + + Args: + req: A urllib2.Request object. + + Returns: + The request passed in. + """ + self.throttle.Sleep(BANDWIDTH_UP) + self.throttle.Sleep(BANDWIDTH_DOWN) + self.AddRequest(BANDWIDTH_UP, req) + return req + + def https_request(self, req): + """Process an HTTPS request. + + If the throttle is over quota, sleep first. Then add request size to + throttle before returning it to be sent. + + Args: + req: A urllib2.Request object. + + Returns: + The request passed in. + """ + self.throttle.Sleep(HTTPS_BANDWIDTH_UP) + self.throttle.Sleep(HTTPS_BANDWIDTH_DOWN) + self.AddRequest(HTTPS_BANDWIDTH_UP, req) + return req + + def http_response(self, unused_req, res): + """Process an HTTP response. + + The size of the response is added to the bandwidth throttle and the request + throttle is incremented by one. + + Args: + unused_req: The urllib2 request for this response. + res: A urllib2 response object. + + Returns: + The response passed in. + """ + self.AddResponse(BANDWIDTH_DOWN, res) + self.throttle.AddTransfer(REQUESTS, 1) + return res + + def https_response(self, unused_req, res): + """Process an HTTPS response. + + The size of the response is added to the bandwidth throttle and the request + throttle is incremented by one. + + Args: + unused_req: The urllib2 request for this response. + res: A urllib2 response object. + + Returns: + The response passed in. + """ + self.AddResponse(HTTPS_BANDWIDTH_DOWN, res) + self.throttle.AddTransfer(HTTPS_REQUESTS, 1) + return res + + +class ThrottledHttpRpcServer(appengine_rpc.HttpRpcServer): + """Provides a simplified RPC-style interface for HTTP requests. + + This RPC server uses a Throttle to prevent exceeding quotas. + """ + + def __init__(self, throttle, *args, **kwargs): + """Initialize a ThrottledHttpRpcServer. + + Also sets request_manager.rpc_server to the ThrottledHttpRpcServer instance. + + Args: + throttle: A Throttles instance. + args: Positional arguments to pass through to + appengine_rpc.HttpRpcServer.__init__ + kwargs: Keyword arguments to pass through to + appengine_rpc.HttpRpcServer.__init__ + """ + self.throttle = throttle + appengine_rpc.HttpRpcServer.__init__(self, *args, **kwargs) + + def _GetOpener(self): + """Returns an OpenerDirector that supports cookies and ignores redirects. + + Returns: + A urllib2.OpenerDirector object. + """ + opener = appengine_rpc.HttpRpcServer._GetOpener(self) + opener.add_handler(ThrottleHandler(self.throttle)) + + return opener + + +def ThrottledHttpRpcServerFactory(throttle): + """Create a factory to produce ThrottledHttpRpcServer for a given throttle. + + Args: + throttle: A Throttle instance to use for the ThrottledHttpRpcServer. + + Returns: + A factory to produce a ThrottledHttpRpcServer. + """ + + def MakeRpcServer(*args, **kwargs): + """Factory to produce a ThrottledHttpRpcServer. + + Args: + args: Positional args to pass to ThrottledHttpRpcServer. + kwargs: Keyword args to pass to ThrottledHttpRpcServer. + + Returns: + A ThrottledHttpRpcServer instance. + """ + kwargs['account_type'] = 'HOSTED_OR_GOOGLE' + kwargs['save_cookies'] = True + rpc_server = ThrottledHttpRpcServer(throttle, *args, **kwargs) + return rpc_server + return MakeRpcServer + + +class Throttler(object): + def PrehookHandler(self, service, call, request, response): + handler = getattr(self, '_Prehook_' + call, None) + if handler: + handler(request, response) + + def PosthookHandler(self, service, call, request, response): + handler = getattr(self, '_Posthook_' + call, None) + if handler: + handler(request, response) + + +def SleepHandler(*throttle_names): + def SleepOnThrottles(self, request, response): + if throttle_names: + for throttle_name in throttle_names: + self._DatastoreThrottler__throttle.Sleep(throttle_name) + else: + self._DatastoreThrottler__throttle.Sleep() + return SleepOnThrottles + + +class DatastoreThrottler(Throttler): + def __init__(self, throttle): + Throttler.__init__(self) + self.__throttle = throttle + + def AddCost(self, cost_proto): + """Add costs from the Cost protobuf.""" + self.__throttle.AddTransfer(INDEX_MODIFICATIONS, cost_proto.index_writes()) + self.__throttle.AddTransfer(ENTITIES_MODIFIED, cost_proto.entity_writes()) + self.__throttle.AddTransfer(BANDWIDTH_UP, cost_proto.entity_write_bytes()) + + + _Prehook_Put = SleepHandler(ENTITIES_MODIFIED, + INDEX_MODIFICATIONS, + BANDWIDTH_UP) + + def _Posthook_Put(self, request, response): + self.AddCost(response.cost()) + + + _Prehook_Get = SleepHandler(ENTITIES_FETCHED) + + def _Posthook_Get(self, request, response): + self.__throttle.AddTransfer(ENTITIES_FETCHED, response.entity_size()) + + + _Prehook_RunQuery = SleepHandler(ENTITIES_FETCHED) + + def _Posthook_RunQuery(self, request, response): + if not response.keys_only(): + self.__throttle.AddTransfer(ENTITIES_FETCHED, response.result_size()) + + + _Prehook_Next = SleepHandler(ENTITIES_FETCHED) + + def _Posthook_Next(self, request, response): + if not response.keys_only(): + self.__throttle.AddTransfer(ENTITIES_FETCHED, response.result_size()) + + + _Prehook_Delete = SleepHandler(ENTITIES_MODIFIED, INDEX_MODIFICATIONS) + + def _Posthook_Delete(self, request, response): + self.AddCost(response.cost()) + + + _Prehook_Commit = SleepHandler() + + def _Posthook_Commit(self, request, response): + self.AddCost(response.cost()) + + +def ThrottleRemoteDatastore(throttle, remote_datastore_stub=None): + """Install the given throttle for the remote datastore stub. + + Args: + throttle: A Throttle instance to limit datastore access rates + remote_datastore_stub: The datstore stub instance to throttle, for + testing purposes. + """ + if not remote_datastore_stub: + remote_datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3') + if not isinstance(remote_datastore_stub, remote_api_stub.RemoteDatastoreStub): + raise remote_api_stub.ConfigurationError('remote_api is not configured.') + throttler = DatastoreThrottler(throttle) + remote_datastore_stub._PreHookHandler = throttler.PrehookHandler + remote_datastore_stub._PostHookHandler = throttler.PosthookHandler diff --git a/google_appengine/google/appengine/ext/remote_api/throttle.pyc b/google_appengine/google/appengine/ext/remote_api/throttle.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c715b4dfa5c3ca0c85ac1cee1a82e835e5fd2f56 GIT binary patch literal 24906 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/ext/search/__init__.py b/google_appengine/google/appengine/ext/search/__init__.py new file mode 100755 index 0000000..a4bd998 --- /dev/null +++ b/google_appengine/google/appengine/ext/search/__init__.py @@ -0,0 +1,420 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Full text indexing and search, implemented in pure python. + +Defines a SearchableModel subclass of db.Model that supports full text +indexing and search, based on the datastore's existing indexes. + +Don't expect too much. First, there's no ranking, which is a killer drawback. +There's also no exact phrase match, substring match, boolean operators, +stemming, or other common full text search features. Finally, support for stop +words (common words that are not indexed) is currently limited to English. + +To be indexed, entities must be created and saved as SearchableModel +instances, e.g.: + + class Article(search.SearchableModel): + text = db.TextProperty() + ... + + article = Article(text=...) + article.save() + +To search the full text index, use the SearchableModel.all() method to get an +instance of SearchableModel.Query, which subclasses db.Query. Use its search() +method to provide a search query, in addition to any other filters or sort +orders, e.g.: + + query = article.all().search('a search query').filter(...).order(...) + for result in query: + ... + +The full text index is stored in a property named __searchable_text_index. + +Specifying multiple indexes and properties to index +--------------------------------------------------- + +By default, one index is created with all string properties. You can define +multiple indexes and specify which properties should be indexed for each by +overriding SearchableProperties() method of model.SearchableModel, for example: + + class Article(search.SearchableModel): + @classmethod + def SearchableProperties(cls): + return [['book', 'author'], ['book']] + +In this example, two indexes will be maintained - one that includes 'book' and +'author' properties, and another one for 'book' property only. They will be +stored in properties named __searchable_text_index_book_author and +__searchable_text_index_book respectively. Note that the index that includes +all properties will not be created unless added explicitly like this: + + @classmethod + def SearchableProperties(cls): + return [['book', 'author'], ['book'], search.ALL_PROPERTIES] + +The default return value of SearchableProperties() is [search.ALL_PROPERTIES] +(one index, all properties). + +To search using a custom-defined index, pass its definition +in 'properties' parameter of 'search': + + Article.all().search('Lem', properties=['book', 'author']) + +Note that the order of properties in the list matters. + +Adding indexes to index.yaml +----------------------------- + +In general, if you just want to provide full text search, you *don't* need to +add any extra indexes to your index.yaml. However, if you want to use search() +in a query *in addition to* an ancestor, filter, or sort order, you'll need to +create an index in index.yaml with the __searchable_text_index property. For +example: + + - kind: Article + properties: + - name: __searchable_text_index + - name: date + direction: desc + ... + +Similarly, if you created a custom index (see above), use the name of the +property it's stored in, e.g. __searchable_text_index_book_author. + +Note that using SearchableModel will noticeable increase the latency of save() +operations, since it writes an index row for each indexable word. This also +means that the latency of save() will increase roughly with the size of the +properties in a given entity. Caveat hacker! +""" + + + + +import re +import string +import sys + +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api import datastore_types +from google.appengine.ext import db +from google.appengine.datastore import datastore_pb + +ALL_PROPERTIES = [] + +class SearchableEntity(datastore.Entity): + """A subclass of datastore.Entity that supports full text indexing. + + Automatically indexes all string and Text properties, using the datastore's + built-in per-property indices. To search, use the SearchableQuery class and + its Search() method. + """ + _FULL_TEXT_INDEX_PROPERTY = '__searchable_text_index' + + _FULL_TEXT_MIN_LENGTH = 3 + + _FULL_TEXT_STOP_WORDS = frozenset([ + 'a', 'about', 'according', 'accordingly', 'affected', 'affecting', 'after', + 'again', 'against', 'all', 'almost', 'already', 'also', 'although', + 'always', 'am', 'among', 'an', 'and', 'any', 'anyone', 'apparently', 'are', + 'arise', 'as', 'aside', 'at', 'away', 'be', 'became', 'because', 'become', + 'becomes', 'been', 'before', 'being', 'between', 'both', 'briefly', 'but', + 'by', 'came', 'can', 'cannot', 'certain', 'certainly', 'could', 'did', 'do', + 'does', 'done', 'during', 'each', 'either', 'else', 'etc', 'ever', 'every', + 'following', 'for', 'found', 'from', 'further', 'gave', 'gets', 'give', + 'given', 'giving', 'gone', 'got', 'had', 'hardly', 'has', 'have', 'having', + 'here', 'how', 'however', 'i', 'if', 'in', 'into', 'is', 'it', 'itself', + 'just', 'keep', 'kept', 'knowledge', 'largely', 'like', 'made', 'mainly', + 'make', 'many', 'might', 'more', 'most', 'mostly', 'much', 'must', 'nearly', + 'necessarily', 'neither', 'next', 'no', 'none', 'nor', 'normally', 'not', + 'noted', 'now', 'obtain', 'obtained', 'of', 'often', 'on', 'only', 'or', + 'other', 'our', 'out', 'owing', 'particularly', 'past', 'perhaps', 'please', + 'poorly', 'possible', 'possibly', 'potentially', 'predominantly', 'present', + 'previously', 'primarily', 'probably', 'prompt', 'promptly', 'put', + 'quickly', 'quite', 'rather', 'readily', 'really', 'recently', 'regarding', + 'regardless', 'relatively', 'respectively', 'resulted', 'resulting', + 'results', 'said', 'same', 'seem', 'seen', 'several', 'shall', 'should', + 'show', 'showed', 'shown', 'shows', 'significantly', 'similar', 'similarly', + 'since', 'slightly', 'so', 'some', 'sometime', 'somewhat', 'soon', + 'specifically', 'state', 'states', 'strongly', 'substantially', + 'successfully', 'such', 'sufficiently', 'than', 'that', 'the', 'their', + 'theirs', 'them', 'then', 'there', 'therefore', 'these', 'they', 'this', + 'those', 'though', 'through', 'throughout', 'to', 'too', 'toward', 'under', + 'unless', 'until', 'up', 'upon', 'use', 'used', 'usefully', 'usefulness', + 'using', 'usually', 'various', 'very', 'was', 'we', 'were', 'what', 'when', + 'where', 'whether', 'which', 'while', 'who', 'whose', 'why', 'widely', + 'will', 'with', 'within', 'without', 'would', 'yet', 'you']) + + _word_delimiter_regex = re.compile('[' + re.escape(string.punctuation) + ']') + + _searchable_properties = [ALL_PROPERTIES] + + def __init__(self, kind_or_entity, word_delimiter_regex=None, *args, + **kwargs): + """Constructor. May be called as a copy constructor. + + If kind_or_entity is a datastore.Entity, copies it into this Entity. + datastore.Get() and Query() returns instances of datastore.Entity, so this + is useful for converting them back to SearchableEntity so that they'll be + indexed when they're stored back in the datastore. + + Otherwise, passes through the positional and keyword args to the + datastore.Entity constructor. + + Args: + kind_or_entity: string or datastore.Entity + word_delimiter_regex: a regex matching characters that delimit words + """ + self._word_delimiter_regex = word_delimiter_regex + if isinstance(kind_or_entity, datastore.Entity): + self._Entity__key = kind_or_entity._Entity__key + self._Entity__unindexed_properties = frozenset(kind_or_entity.unindexed_properties()) + if isinstance(kind_or_entity, SearchableEntity): + if getattr(kind_or_entity, '_searchable_properties', None) is not None: + self._searchable_properties = kind_or_entity._searchable_properties + self.update(kind_or_entity) + else: + super(SearchableEntity, self).__init__(kind_or_entity, *args, **kwargs) + + def _ToPb(self): + """Rebuilds the full text index, then delegates to the superclass. + + Returns: + entity_pb.Entity + """ + for properties_to_index in self._searchable_properties: + index_property_name = SearchableEntity.IndexPropertyName(properties_to_index) + if index_property_name in self: + del self[index_property_name] + + + if not properties_to_index: + properties_to_index = self.keys() + + index = set() + for name in properties_to_index: + if not self.has_key(name): + continue + + values = self[name] + if not isinstance(values, list): + values = [values] + + if (isinstance(values[0], basestring) and + not isinstance(values[0], datastore_types.Blob)): + for value in values: + index.update(SearchableEntity._FullTextIndex( + value, self._word_delimiter_regex)) + + index_list = list(index) + if index_list: + self[index_property_name] = index_list + + return super(SearchableEntity, self)._ToPb() + + @classmethod + def _FullTextIndex(cls, text, word_delimiter_regex=None): + """Returns a set of keywords appropriate for full text indexing. + + See SearchableQuery.Search() for details. + + Args: + text: string + + Returns: + set of strings + """ + + if word_delimiter_regex is None: + word_delimiter_regex = cls._word_delimiter_regex + + if text: + datastore_types.ValidateString(text, 'text', max_len=sys.maxint) + text = word_delimiter_regex.sub(' ', text) + words = text.lower().split() + + words = set(unicode(w) for w in words) + + words -= cls._FULL_TEXT_STOP_WORDS + for word in list(words): + if len(word) < cls._FULL_TEXT_MIN_LENGTH: + words.remove(word) + + else: + words = set() + + return words + + @classmethod + def IndexPropertyName(cls, properties): + """Given index definition, returns the name of the property to put it in.""" + name = SearchableEntity._FULL_TEXT_INDEX_PROPERTY + + if properties: + name += '_' + '_'.join(properties) + + return name + + +class SearchableQuery(datastore.Query): + """A subclass of datastore.Query that supports full text search. + + Only searches over entities that were created and stored using the + SearchableEntity or SearchableModel classes. + """ + + def Search(self, search_query, word_delimiter_regex=None, + properties=ALL_PROPERTIES): + """Add a search query. This may be combined with filters. + + Note that keywords in the search query will be silently dropped if they + are stop words or too short, ie if they wouldn't be indexed. + + Args: + search_query: string + + Returns: + # this query + SearchableQuery + """ + datastore_types.ValidateString(search_query, 'search query') + self._search_query = search_query + self._word_delimiter_regex = word_delimiter_regex + self._properties = properties + return self + + def _ToPb(self, *args, **kwds): + """Adds filters for the search query, then delegates to the superclass. + + Mimics Query._ToPb()'s signature. Raises BadFilterError if a filter on the + index property already exists. + + Returns: + datastore_pb.Query + """ + + properties = getattr(self, "_properties", ALL_PROPERTIES) + + index_property_name = SearchableEntity.IndexPropertyName(properties) + if index_property_name in self: + raise datastore_errors.BadFilterError( + '%s is a reserved name.' % index_property_name) + + pb = super(SearchableQuery, self)._ToPb(*args, **kwds) + + if hasattr(self, '_search_query'): + keywords = SearchableEntity._FullTextIndex( + self._search_query, self._word_delimiter_regex) + for keyword in keywords: + filter = pb.add_filter() + filter.set_op(datastore_pb.Query_Filter.EQUAL) + prop = filter.add_property() + prop.set_name(index_property_name) + prop.set_multiple(len(keywords) > 1) + prop.mutable_value().set_stringvalue(unicode(keyword).encode('utf-8')) + + return pb + + +class SearchableMultiQuery(datastore.MultiQuery): + """A multiquery that supports Search() by searching subqueries.""" + + def Search(self, *args, **kwargs): + """Add a search query, by trying to add it to all subqueries. + + Args: + args: Passed to Search on each subquery. + kwargs: Passed to Search on each subquery. + + Returns: + self for consistency with SearchableQuery. + """ + for q in self: + q.Search(*args, **kwargs) + return self + + +class SearchableModel(db.Model): + """A subclass of db.Model that supports full text search and indexing. + + Automatically indexes all string-based properties. To search, use the all() + method to get a SearchableModel.Query, then use its search() method. + + Override SearchableProperties() to define properties to index and/or multiple + indexes (see the file's comment). + """ + + @classmethod + def SearchableProperties(cls): + return [ALL_PROPERTIES] + + class Query(db.Query): + """A subclass of db.Query that supports full text search.""" + _search_query = None + _properties = None + + def search(self, search_query, properties=ALL_PROPERTIES): + """Adds a full text search to this query. + + Args: + search_query, a string containing the full text search query. + + Returns: + self + """ + self._search_query = search_query + self._properties = properties + + if self._properties not in getattr(self, '_searchable_properties', [ALL_PROPERTIES]): + raise datastore_errors.BadFilterError( + '%s does not have a corresponding index. Please add it to' + 'the SEARCHABLE_PROPERTIES list' % self._properties) + + return self + + def _get_query(self): + """Wraps db.Query._get_query() and injects SearchableQuery.""" + query = db.Query._get_query(self, + _query_class=SearchableQuery, + _multi_query_class=SearchableMultiQuery) + if self._search_query: + query.Search(self._search_query, properties=self._properties) + return query + + def _populate_internal_entity(self): + """Wraps db.Model._populate_internal_entity() and injects + SearchableEntity.""" + entity = db.Model._populate_internal_entity(self, + _entity_class=SearchableEntity) + entity._searchable_properties = self.SearchableProperties() + return entity + + @classmethod + def from_entity(cls, entity): + """Wraps db.Model.from_entity() and injects SearchableEntity.""" + if not isinstance(entity, SearchableEntity): + entity = SearchableEntity(entity) + return super(SearchableModel, cls).from_entity(entity) + + @classmethod + def all(cls): + """Returns a SearchableModel.Query for this kind.""" + query = SearchableModel.Query(cls) + query._searchable_properties = cls.SearchableProperties() + return query diff --git a/google_appengine/google/appengine/ext/search/__init__.pyc b/google_appengine/google/appengine/ext/search/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..062de4c681d8734c89a2b14a8951493897a838e4 GIT binary patch literal 17410 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
' + 'Name: ' + '
') + + class HelloPage(webapp.RequestHandler): + def post(self): + self.response.headers['Content-Type'] = 'text/plain' + self.response.out.write('Hello, %s' % self.request.get('name')) + + application = webapp.WSGIApplication([ + ('/', MainPage), + ('/hello', HelloPage) + ], debug=True) + + server = wsgiref.simple_server.make_server('', 8080, application) + print 'Serving on port 8080...' + server.serve_forever() + +The WSGIApplication class maps URI regular expressions to your RequestHandler +classes. It is a WSGI-compatible application object, so you can use it in +conjunction with wsgiref to make your web application into, e.g., a CGI +script or a simple HTTP server, as in the example above. + +The framework does not support streaming output. All output from a response +is stored in memory before it is written. +""" + + +import cgi +import StringIO +import logging +import re +import sys +import traceback +import urlparse +import webob +import wsgiref.handlers +import wsgiref.headers +import wsgiref.util + +wsgiref.handlers.BaseHandler.os_environ = {} + +RE_FIND_GROUPS = re.compile('\(.*?\)') +_CHARSET_RE = re.compile(r';\s*charset=([^;\s]*)', re.I) + +class Error(Exception): + """Base of all exceptions in the webapp module.""" + pass + + +class NoUrlFoundError(Error): + """Thrown when RequestHandler.get_url() fails.""" + pass + + +class Request(webob.Request): + """Abstraction for an HTTP request. + + Properties: + uri: the complete URI requested by the user + scheme: 'http' or 'https' + host: the host, including the port + path: the path up to the ';' or '?' in the URL + parameters: the part of the URL between the ';' and the '?', if any + query: the part of the URL after the '?' + + You can access parsed query and POST values with the get() method; do not + parse the query string yourself. + """ + + request_body_tempfile_limit = 0 + + uri = property(lambda self: self.url) + query = property(lambda self: self.query_string) + + def __init__(self, environ): + """Constructs a Request object from a WSGI environment. + + If the charset isn't specified in the Content-Type header, defaults + to UTF-8. + + Args: + environ: A WSGI-compliant environment dictionary. + """ + match = _CHARSET_RE.search(environ.get('CONTENT_TYPE', '')) + if match: + charset = match.group(1).lower() + else: + charset = 'utf-8' + + webob.Request.__init__(self, environ, charset=charset, + unicode_errors= 'ignore', decode_param_names=True) + + def get(self, argument_name, default_value='', allow_multiple=False): + """Returns the query or POST argument with the given name. + + We parse the query string and POST payload lazily, so this will be a + slower operation on the first call. + + Args: + argument_name: the name of the query or POST argument + default_value: the value to return if the given argument is not present + allow_multiple: return a list of values with the given name (deprecated) + + Returns: + If allow_multiple is False (which it is by default), we return the first + value with the given name given in the request. If it is True, we always + return an list. + """ + param_value = self.get_all(argument_name) + if allow_multiple: + return param_value + else: + if len(param_value) > 0: + return param_value[0] + else: + return default_value + + def get_all(self, argument_name): + """Returns a list of query or POST arguments with the given name. + + We parse the query string and POST payload lazily, so this will be a + slower operation on the first call. + + Args: + argument_name: the name of the query or POST argument + + Returns: + A (possibly empty) list of values. + """ + if self.charset: + argument_name = argument_name.encode(self.charset) + + param_value = self.params.getall(argument_name) + + for i in xrange(len(param_value)): + if isinstance(param_value[i], cgi.FieldStorage): + param_value[i] = param_value[i].value + + return param_value + + def arguments(self): + """Returns a list of the arguments provided in the query and/or POST. + + The return value is a list of strings. + """ + return list(set(self.params.keys())) + + def get_range(self, name, min_value=None, max_value=None, default=0): + """Parses the given int argument, limiting it to the given range. + + Args: + name: the name of the argument + min_value: the minimum int value of the argument (if any) + max_value: the maximum int value of the argument (if any) + default: the default value of the argument if it is not given + + Returns: + An int within the given range for the argument + """ + try: + value = int(self.get(name, default)) + except ValueError: + value = default + if max_value != None: + value = min(value, max_value) + if min_value != None: + value = max(value, min_value) + return value + + +class Response(object): + """Abstraction for an HTTP response. + + Properties: + out: file pointer for the output stream + headers: wsgiref.headers.Headers instance representing the output headers + """ + def __init__(self): + """Constructs a response with the default settings.""" + self.out = StringIO.StringIO() + self.__wsgi_headers = [] + self.headers = wsgiref.headers.Headers(self.__wsgi_headers) + self.headers['Content-Type'] = 'text/html; charset=utf-8' + self.headers['Cache-Control'] = 'no-cache' + self.set_status(200) + + def set_status(self, code, message=None): + """Sets the HTTP status code of this response. + + Args: + message: the HTTP status string to use + + If no status string is given, we use the default from the HTTP/1.1 + specification. + """ + if not message: + message = Response.http_status_message(code) + self.__status = (code, message) + + def clear(self): + """Clears all data written to the output stream so that it is empty.""" + self.out.seek(0) + self.out.truncate(0) + + def wsgi_write(self, start_response): + """Writes this response using WSGI semantics with the given WSGI function. + + Args: + start_response: the WSGI-compatible start_response function + """ + body = self.out.getvalue() + if isinstance(body, unicode): + body = body.encode('utf-8') + elif self.headers.get('Content-Type', '').endswith('; charset=utf-8'): + try: + body.decode('utf-8') + except UnicodeError, e: + logging.warning('Response written is not UTF-8: %s', e) + + if (self.headers.get('Cache-Control') == 'no-cache' and + not self.headers.get('Expires')): + self.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' + self.headers['Content-Length'] = str(len(body)) + write = start_response('%d %s' % self.__status, self.__wsgi_headers) + write(body) + self.out.close() + + def http_status_message(code): + """Returns the default HTTP status message for the given code. + + Args: + code: the HTTP code for which we want a message + """ + if not Response.__HTTP_STATUS_MESSAGES.has_key(code): + raise Error('Invalid HTTP status code: %d' % code) + return Response.__HTTP_STATUS_MESSAGES[code] + http_status_message = staticmethod(http_status_message) + + __HTTP_STATUS_MESSAGES = { + 100: 'Continue', + 101: 'Switching Protocols', + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-Authoritative Information', + 204: 'No Content', + 205: 'Reset Content', + 206: 'Partial Content', + 300: 'Multiple Choices', + 301: 'Moved Permanently', + 302: 'Moved Temporarily', + 303: 'See Other', + 304: 'Not Modified', + 305: 'Use Proxy', + 306: 'Unused', + 307: 'Temporary Redirect', + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Time-out', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Request Entity Too Large', + 414: 'Request-URI Too Large', + 415: 'Unsupported Media Type', + 416: 'Requested Range Not Satisfiable', + 417: 'Expectation Failed', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Time-out', + 505: 'HTTP Version not supported' + } + + +class RequestHandler(object): + """Our base HTTP request handler. Clients should subclass this class. + + Subclasses should override get(), post(), head(), options(), etc to handle + different HTTP methods. + """ + def initialize(self, request, response): + """Initializes this request handler with the given Request and Response.""" + self.request = request + self.response = response + + def get(self, *args): + """Handler method for GET requests.""" + self.error(405) + + def post(self, *args): + """Handler method for POST requests.""" + self.error(405) + + def head(self, *args): + """Handler method for HEAD requests.""" + self.error(405) + + def options(self, *args): + """Handler method for OPTIONS requests.""" + self.error(405) + + def put(self, *args): + """Handler method for PUT requests.""" + self.error(405) + + def delete(self, *args): + """Handler method for DELETE requests.""" + self.error(405) + + def trace(self, *args): + """Handler method for TRACE requests.""" + self.error(405) + + def error(self, code): + """Clears the response output stream and sets the given HTTP error code. + + Args: + code: the HTTP status error code (e.g., 501) + """ + self.response.set_status(code) + self.response.clear() + + def redirect(self, uri, permanent=False): + """Issues an HTTP redirect to the given relative URL. + + Args: + uri: a relative or absolute URI (e.g., '../flowers.html') + permanent: if true, we use a 301 redirect instead of a 302 redirect + """ + if permanent: + self.response.set_status(301) + else: + self.response.set_status(302) + absolute_url = urlparse.urljoin(self.request.uri, uri) + self.response.headers['Location'] = str(absolute_url) + self.response.clear() + + def handle_exception(self, exception, debug_mode): + """Called if this handler throws an exception during execution. + + The default behavior is to call self.error(500) and print a stack trace + if debug_mode is True. + + Args: + exception: the exception that was thrown + debug_mode: True if the web application is running in debug mode + """ + self.error(500) + logging.exception(exception) + if debug_mode: + lines = ''.join(traceback.format_exception(*sys.exc_info())) + self.response.clear() + self.response.out.write('
%s
' % (cgi.escape(lines, quote=True))) + + @classmethod + def get_url(cls, *args, **kargs): + """Returns the url for the given handler. + + The default implementation uses the patterns passed to the active + WSGIApplication and the django urlresolvers module to create a url. + However, it is different from urlresolvers.reverse() in the following ways: + - It does not try to resolve handlers via module loading + - It does not support named arguments + - It performs some post-prosessing on the url to remove some regex + operators that urlresolvers.reverse_helper() seems to miss. + - It will try to fill in the left-most missing arguments with the args + used in the active request. + + Args: + args: Parameters for the url pattern's groups. + kwargs: Optionally contains 'implicit_args' that can either be a boolean + or a tuple. When it is True, it will use the arguments to the + active request as implicit arguments. When it is False (default), + it will not use any implicit arguments. When it is a tuple, it + will use the tuple as the implicit arguments. + the left-most args if some are missing from args. + + Returns: + The url for this handler/args combination. + + Raises: + NoUrlFoundError: No url pattern for this handler has the same + number of args that were passed in. + """ + + app = WSGIApplication.active_instance + pattern_map = app._pattern_map + + implicit_args = kargs.get('implicit_args', ()) + if implicit_args == True: + implicit_args = app.current_request_args + + min_params = len(args) + + urlresolvers = None + + for pattern_tuple in pattern_map.get(cls, ()): + num_params_in_pattern = pattern_tuple[1] + if num_params_in_pattern < min_params: + continue + + if urlresolvers is None: + from django.core import urlresolvers + + try: + num_implicit_args = max(0, num_params_in_pattern - len(args)) + merged_args = implicit_args[:num_implicit_args] + args + url = urlresolvers.reverse_helper(pattern_tuple[0], *merged_args) + url = url.replace('\\', '') + url = url.replace('?', '') + return url + except urlresolvers.NoReverseMatch: + continue + + logging.warning('get_url failed for Handler name: %r, Args: %r', + cls.__name__, args) + raise NoUrlFoundError + + +class WSGIApplication(object): + """Wraps a set of webapp RequestHandlers in a WSGI-compatible application. + + To use this class, pass a list of (URI regular expression, RequestHandler) + pairs to the constructor, and pass the class instance to a WSGI handler. + See the example in the module comments for details. + + The URL mapping is first-match based on the list ordering. + """ + + REQUEST_CLASS = Request + RESPONSE_CLASS = Response + + def __init__(self, url_mapping, debug=False): + """Initializes this application with the given URL mapping. + + Args: + url_mapping: list of (URI regular expression, RequestHandler) pairs + (e.g., [('/', ReqHan)]) + debug: if true, we send Python stack traces to the browser on errors + """ + self._init_url_mappings(url_mapping) + self.__debug = debug + WSGIApplication.active_instance = self + self.current_request_args = () + + def __call__(self, environ, start_response): + """Called by WSGI when a request comes in.""" + request = self.REQUEST_CLASS(environ) + response = self.RESPONSE_CLASS() + + WSGIApplication.active_instance = self + + handler = None + groups = () + for regexp, handler_class in self._url_mapping: + match = regexp.match(request.path) + if match: + handler = handler_class() + handler.initialize(request, response) + groups = match.groups() + break + + self.current_request_args = groups + + if handler: + try: + method = environ['REQUEST_METHOD'] + if method == 'GET': + handler.get(*groups) + elif method == 'POST': + handler.post(*groups) + elif method == 'HEAD': + handler.head(*groups) + elif method == 'OPTIONS': + handler.options(*groups) + elif method == 'PUT': + handler.put(*groups) + elif method == 'DELETE': + handler.delete(*groups) + elif method == 'TRACE': + handler.trace(*groups) + else: + handler.error(501) + except Exception, e: + handler.handle_exception(e, self.__debug) + else: + response.set_status(404) + + response.wsgi_write(start_response) + return [''] + + def _init_url_mappings(self, handler_tuples): + """Initializes the maps needed for mapping urls to handlers and handlers + to urls. + + Args: + handler_tuples: list of (URI, RequestHandler) pairs. + """ + + handler_map = {} + pattern_map = {} + url_mapping = [] + + for regexp, handler in handler_tuples: + + try: + handler_name = handler.__name__ + except AttributeError: + pass + else: + handler_map[handler_name] = handler + + if not regexp.startswith('^'): + regexp = '^' + regexp + if not regexp.endswith('$'): + regexp += '$' + + if regexp == '^/form$': + logging.warning('The URL "/form" is reserved and will not be matched.') + + compiled = re.compile(regexp) + url_mapping.append((compiled, handler)) + + num_groups = len(RE_FIND_GROUPS.findall(regexp)) + handler_patterns = pattern_map.setdefault(handler, []) + handler_patterns.append((compiled, num_groups)) + + self._handler_map = handler_map + self._pattern_map = pattern_map + self._url_mapping = url_mapping + + def get_registered_handler_by_name(self, handler_name): + """Returns the handler given the handler's name. + + This uses the application's url mapping. + + Args: + handler_name: The __name__ of a handler to return. + + Returns: + The handler with the given name. + + Raises: + KeyError: If the handler name is not found in the parent application. + """ + try: + return self._handler_map[handler_name] + except: + logging.error('Handler does not map to any urls: %s', handler_name) + raise diff --git a/google_appengine/google/appengine/ext/webapp/__init__.pyc b/google_appengine/google/appengine/ext/webapp/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd5d72e3fce320e95dc447bb21670e767b82070a GIT binary patch literal 22826 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 end. + - start < 0 and end is also provided. + - end < 0 + - If index provided AND using the HTTP header, they don't match. + This is a safeguard. + """ + if end is not None and start is None: + raise ValueError('May not specify end value without start.') + + use_indexes = start is not None + if use_indexes: + if end is not None: + if start > end: + raise ValueError('start must be < end.') + + range_indexes = byterange.Range.serialize_bytes(_BYTES_UNIT, [(start, end)]) + + if use_range_set and use_range and use_indexes: + if range_header != range_indexes: + raise ValueError('May not provide non-equivalent range indexes and ' + 'range headers: (header) %s != (indexes) %s' + % (range_header, range_indexes)) + + if use_range and range_header is not None: + return range_header + elif use_indexes: + return range_indexes + else: + return None + + +class BlobstoreDownloadHandler(webapp.RequestHandler): + """Base class for creating handlers that may send blobs to users.""" + + + __use_range_unset = object() + def send_blob(self, + blob_key_or_info, + content_type=None, + save_as=None, + start=None, + end=None, + **kwargs): + """Send a blob-response based on a blob_key. + + Sets the correct response header for serving a blob. If BlobInfo + is provided and no content_type specified, will set request content type + to BlobInfo's content type. + + Args: + blob_key_or_info: BlobKey or BlobInfo record to serve. + content_type: Content-type to override when known. + save_as: If True, and BlobInfo record is provided, use BlobInfos + filename to save-as. If string is provided, use string as filename. + If None or False, do not send as attachment. + start: Start index of content-range to send. + end: End index of content-range to send. End index is inclusive. + use_range: Use provided content range from requests Range header. + Mutually exclusive to start and end. + + Raises: + ValueError on invalid save_as parameter. + """ + if set(kwargs) - _SEND_BLOB_PARAMETERS: + invalid_keywords = [] + for keyword in kwargs: + if keyword not in _SEND_BLOB_PARAMETERS: + invalid_keywords.append(keyword) + if len(invalid_keywords) == 1: + raise TypeError('send_blob got unexpected keyword argument %s.' + % invalid_keywords[0]) + else: + raise TypeError('send_blob got unexpected keyword arguments: %s' + % sorted(invalid_keywords)) + + + use_range = kwargs.get('use_range', self.__use_range_unset) + use_range_set = use_range is not self.__use_range_unset + + range_header = _check_ranges(start, + end, + use_range_set, + use_range, + self.request.headers.get('range', None)) + + if range_header is not None: + self.response.headers[blobstore.BLOB_RANGE_HEADER] = range_header + + if isinstance(blob_key_or_info, blobstore.BlobInfo): + blob_key = blob_key_or_info.key() + blob_info = blob_key_or_info + else: + blob_key = blob_key_or_info + blob_info = None + + self.response.headers[blobstore.BLOB_KEY_HEADER] = str(blob_key) + + if content_type: + if isinstance(content_type, unicode): + content_type = content_type.encode('utf-8') + self.response.headers['Content-Type'] = content_type + else: + del self.response.headers['Content-Type'] + + def send_attachment(filename): + if isinstance(filename, unicode): + filename = filename.encode('utf-8') + self.response.headers['Content-Disposition'] = ( + _CONTENT_DISPOSITION_FORMAT % filename) + + if save_as: + if isinstance(save_as, basestring): + send_attachment(save_as) + elif blob_info and save_as is True: + send_attachment(blob_info.filename) + else: + if not blob_info: + raise ValueError('Expected BlobInfo value for blob_key_or_info.') + else: + raise ValueError('Unexpected value for save_as.') + + self.response.clear() + + def get_range(self): + """Get range from header if it exists. + + Returns: + Tuple (start, end): + start: Start index. None if there is None. + end: End index. None if there is None. + None if there is no request header. + + Raises: + UnsupportedRangeFormatError: If the range format in the header is + valid, but not supported. + RangeFormatError: If the range format in the header is not valid. + """ + range_header = self.request.headers.get('range', None) + if range_header is None: + return None + + try: + original_stdout = sys.stdout + sys.stdout = cStringIO.StringIO() + try: + parsed_range = byterange.Range.parse_bytes(range_header) + finally: + sys.stdout = original_stdout + except TypeError, err: + raise RangeFormatError('Invalid range header: %s' % err) + if parsed_range is None: + raise RangeFormatError('Invalid range header: %s' % range_header) + + units, ranges = parsed_range + if len(ranges) != 1: + raise UnsupportedRangeFormatError( + 'Unable to support multiple range values in Range header.') + + if units != _BYTES_UNIT: + raise UnsupportedRangeFormatError( + 'Invalid unit in range header type: %s', range_header) + + return ranges[0] + + +class BlobstoreUploadHandler(webapp.RequestHandler): + """Base class for creation blob upload handlers.""" + + def __init__(self): + super(BlobstoreUploadHandler, self).__init__() + self.__uploads = None + + def get_uploads(self, field_name=None): + """Get uploads sent to this handler. + + Args: + field_name: Only select uploads that were sent as a specific field. + + Returns: + A list of BlobInfo records corresponding to each upload. + Empty list if there are no blob-info records for field_name. + """ + if self.__uploads is None: + self.__uploads = {} + for key, value in self.request.params.items(): + if isinstance(value, cgi.FieldStorage): + if 'blob-key' in value.type_options: + self.__uploads.setdefault(key, []).append( + blobstore.parse_blob_info(value)) + + if field_name: + try: + return list(self.__uploads[field_name]) + except KeyError: + return [] + else: + results = [] + for uploads in self.__uploads.itervalues(): + results += uploads + return results diff --git a/google_appengine/google/appengine/ext/webapp/mail_handlers.py b/google_appengine/google/appengine/ext/webapp/mail_handlers.py new file mode 100755 index 0000000..51077bf --- /dev/null +++ b/google_appengine/google/appengine/ext/webapp/mail_handlers.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Handler library for inbound Mail API. + +Contains handlers to help with receiving mail. + + InboundMailHandler: Has helper method for easily setting up + email recievers. +""" + + + + + +from google.appengine.api import mail +from google.appengine.ext import webapp + + +MAIL_HANDLER_URL_PATTERN = '/_ah/mail/.+' + + +class InboundMailHandler(webapp.RequestHandler): + """Base class for inbound mail handlers. + + Example: + + # Sub-class overrides receive method. + class HelloReceiver(InboundMailHandler): + + def receive(self, mail_message): + logging.info('Received greeting from %s: %s' % (mail_message.sender, + mail_message.body)) + + + # Map mail handler to appliction. + application = webapp.WSGIApplication([ + HelloReceiver.mapping(), + ]) + """ + + def post(self): + """Transforms body to email request.""" + self.receive(mail.InboundEmailMessage(self.request.body)) + + def receive(self, mail_message): + """Receive an email message. + + Override this method to implement an email receiver. + + Args: + mail_message: InboundEmailMessage instance representing received + email. + """ + pass + + @classmethod + def mapping(cls): + """Convenience method to map handler class to application. + + Returns: + Mapping from email URL to inbound mail handler class. + """ + return MAIL_HANDLER_URL_PATTERN, cls diff --git a/google_appengine/google/appengine/ext/webapp/template.py b/google_appengine/google/appengine/ext/webapp/template.py new file mode 100755 index 0000000..2cd8fd3 --- /dev/null +++ b/google_appengine/google/appengine/ext/webapp/template.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A simple wrapper for Django templates. + +The main purpose of this module is to hide all of the package import pain +you normally have to go through to get Django to work. We expose the Django +Template and Context classes from this module, handling the import nonsense +on behalf of clients. + +Typical usage: + + from google.appengine.ext.webapp import template + print template.render('templates/index.html', {'foo': 'bar'}) + +Django uses a global setting for the directory in which it looks for templates. +This is not natural in the context of the webapp module, so our load method +takes in a complete template path, and we set these settings on the fly +automatically. Because we have to set and use a global setting on every +method call, this module is not thread safe, though that is not an issue +for applications. + +Django template documentation is available at: +http://www.djangoproject.com/documentation/templates/ +""" + + + + + +import md5 +import os + +try: + from django import v0_96 +except ImportError: + pass +import django + +import django.conf +try: + django.conf.settings.configure( + DEBUG=False, + TEMPLATE_DEBUG=False, + TEMPLATE_LOADERS=( + 'django.template.loaders.filesystem.load_template_source', + ), + ) +except (EnvironmentError, RuntimeError): + pass +import django.template +import django.template.loader + +from google.appengine.ext import webapp + +def render(template_path, template_dict, debug=False): + """Renders the template at the given path with the given dict of values. + + Example usage: + render("templates/index.html", {"name": "Bret", "values": [1, 2, 3]}) + + Args: + template_path: path to a Django template + template_dict: dictionary of values to apply to the template + """ + t = load(template_path, debug) + return t.render(Context(template_dict)) + + +template_cache = {} +def load(path, debug=False): + """Loads the Django template from the given path. + + It is better to use this function than to construct a Template using the + class below because Django requires you to load the template with a method + if you want imports and extends to work in the template. + """ + abspath = os.path.abspath(path) + + if not debug: + template = template_cache.get(abspath, None) + else: + template = None + + if not template: + directory, file_name = os.path.split(abspath) + new_settings = { + 'TEMPLATE_DIRS': (directory,), + 'TEMPLATE_DEBUG': debug, + 'DEBUG': debug, + } + old_settings = _swap_settings(new_settings) + try: + template = django.template.loader.get_template(file_name) + finally: + _swap_settings(old_settings) + + if not debug: + template_cache[abspath] = template + + def wrap_render(context, orig_render=template.render): + URLNode = django.template.defaulttags.URLNode + save_urlnode_render = URLNode.render + old_settings = _swap_settings(new_settings) + try: + URLNode.render = _urlnode_render_replacement + return orig_render(context) + finally: + _swap_settings(old_settings) + URLNode.render = save_urlnode_render + + template.render = wrap_render + + return template + + +def _swap_settings(new): + """Swap in selected Django settings, returning old settings. + + Example: + save = _swap_settings({'X': 1, 'Y': 2}) + try: + ...new settings for X and Y are in effect here... + finally: + _swap_settings(save) + + Args: + new: A dict containing settings to change; the keys should + be setting names and the values settings values. + + Returns: + Another dict structured the same was as the argument containing + the original settings. Original settings that were not set at all + are returned as None, and will be restored as None by the + 'finally' clause in the example above. This shouldn't matter; we + can't delete settings that are given as None, since None is also a + legitimate value for some settings. Creating a separate flag value + for 'unset' settings seems overkill as there is no known use case. + """ + settings = django.conf.settings + old = {} + for key, value in new.iteritems(): + old[key] = getattr(settings, key, None) + setattr(settings, key, value) + return old + + +def create_template_register(): + """Used to extend the Django template library with custom filters and tags. + + To extend the template library with a custom filter module, create a Python + module, and create a module-level variable named "register", and register + all custom filters to it as described at + http://www.djangoproject.com/documentation/templates_python/ + #extending-the-template-system: + + templatefilters.py + ================== + register = webapp.template.create_template_register() + + def cut(value, arg): + return value.replace(arg, '') + register.filter(cut) + + Then, register the custom template module with the register_template_library + function below in your application module: + + myapp.py + ======== + webapp.template.register_template_library('templatefilters') + """ + return django.template.Library() + + +def register_template_library(package_name): + """Registers a template extension module to make it usable in templates. + + See the documentation for create_template_register for more information.""" + if not django.template.libraries.get(package_name, None): + django.template.add_to_builtins(package_name) + + +Template = django.template.Template +Context = django.template.Context + + +def _urlnode_render_replacement(self, context): + """Replacement for django's {% url %} block. + + This version uses WSGIApplication's url mapping to create urls. + + Examples: + +
+ {% url MyPageHandler implicit_args=False %} + {% url MyPageHandler "calendar" %} + {% url MyPageHandler "jsmith","calendar" %} + """ + args = [arg.resolve(context) for arg in self.args] + try: + app = webapp.WSGIApplication.active_instance + handler = app.get_registered_handler_by_name(self.view_name) + return handler.get_url(implicit_args=True, *args) + except webapp.NoUrlFoundError: + return '' diff --git a/google_appengine/google/appengine/ext/webapp/util.py b/google_appengine/google/appengine/ext/webapp/util.py new file mode 100755 index 0000000..93e3608 --- /dev/null +++ b/google_appengine/google/appengine/ext/webapp/util.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Convience functions for the Webapp framework.""" + + + + + +__all__ = ['login_required', + 'run_wsgi_app', + 'add_wsgi_middleware', + 'run_bare_wsgi_app', + ] + +import os +import sys +import wsgiref.util + +from google.appengine.api import users +from google.appengine.api import lib_config +from google.appengine.ext import webapp + + +def login_required(handler_method): + """A decorator to require that a user be logged in to access a handler. + + To use it, decorate your get() method like this: + + @login_required + def get(self): + user = users.get_current_user(self) + self.response.out.write('Hello, ' + user.nickname()) + + We will redirect to a login page if the user is not logged in. We always + redirect to the request URI, and Google Accounts only redirects back as a GET + request, so this should not be used for POSTs. + """ + def check_login(self, *args): + if self.request.method != 'GET': + raise webapp.Error('The check_login decorator can only be used for GET ' + 'requests') + user = users.get_current_user() + if not user: + self.redirect(users.create_login_url(self.request.uri)) + return + else: + handler_method(self, *args) + return check_login + + +_config_handle = lib_config.register( + 'webapp', + {'add_wsgi_middleware': lambda app: app}) + + +def run_wsgi_app(application): + """Runs your WSGI-compliant application object in a CGI environment. + + Compared to wsgiref.handlers.CGIHandler().run(application), this + function takes some shortcuts. Those are possible because the + app server makes stronger promises than the CGI standard. + + Also, this function may wrap custom WSGI middleware around the + application. (You can use run_bare_wsgi_app() to run an application + without adding WSGI middleware, and add_wsgi_middleware() to wrap + the configured WSGI middleware around an application without running + it. This function is merely a convenient combination of the latter + two.) + + To configure custom WSGI middleware, define a function + webapp_add_wsgi_middleware(app) to your appengine_config.py file in + your application root directory: + + def webapp_add_wsgi_middleware(app): + app = MiddleWareClassOne(app) + app = MiddleWareClassTwo(app) + return app + + You must import the middleware classes elsewhere in the file. If + the function is not found, no WSGI middleware is added. + """ + run_bare_wsgi_app(add_wsgi_middleware(application)) + + +def add_wsgi_middleware(application): + """Wrap WSGI middleware around a WSGI application object.""" + return _config_handle.add_wsgi_middleware(application) + + +def run_bare_wsgi_app(application): + """Like run_wsgi_app() but doesn't add WSGI middleware.""" + env = dict(os.environ) + env["wsgi.input"] = sys.stdin + env["wsgi.errors"] = sys.stderr + env["wsgi.version"] = (1, 0) + env["wsgi.run_once"] = True + env["wsgi.url_scheme"] = wsgiref.util.guess_scheme(env) + env["wsgi.multithread"] = False + env["wsgi.multiprocess"] = False + result = application(env, _start_response) + if result is not None: + for data in result: + sys.stdout.write(data) + + +def _start_response(status, headers, exc_info=None): + """A start_response() callable as specified by PEP 333""" + if exc_info is not None: + raise exc_info[0], exc_info[1], exc_info[2] + print "Status: %s" % status + for name, val in headers: + print "%s: %s" % (name, val) + print + return sys.stdout.write diff --git a/google_appengine/google/appengine/ext/webapp/xmpp_handlers.py b/google_appengine/google/appengine/ext/webapp/xmpp_handlers.py new file mode 100755 index 0000000..99840bf --- /dev/null +++ b/google_appengine/google/appengine/ext/webapp/xmpp_handlers.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""XMPP webapp handler classes. + +This module provides handler classes for XMPP bots, including both basic +messaging functionality and a command handler for commands such as "/foo bar" +""" + + + +import logging +from google.appengine.api import xmpp +from google.appengine.ext import webapp + + +class BaseHandler(webapp.RequestHandler): + """A webapp baseclass for XMPP handlers. + + Implements a straightforward message delivery pattern. When a message is + received, message_received is called with a Message object that encapsulates + the relevant details. Users can reply using the standard XMPP API, or the + convenient .reply() method on the Message object. + """ + + def message_received(self, message): + """Called when a message is sent to the XMPP bot. + + Args: + message: Message: The message that was sent by the user. + """ + raise NotImplementedError() + + def handle_exception(self, exception, debug_mode): + """Called if this handler throws an exception during execution. + + Args: + exception: the exception that was thrown + debug_mode: True if the web application is running in debug mode + """ + super(BaseHandler, self).handle_exception(exception, debug_mode) + if self.xmpp_message: + self.xmpp_message.reply('Oops. Something went wrong.') + + def post(self): + try: + self.xmpp_message = xmpp.Message(self.request.POST) + except xmpp.InvalidMessageError, e: + logging.error("Invalid XMPP request: Missing required field %s", e[0]) + return + self.message_received(self.xmpp_message) + + +class CommandHandlerMixin(object): + """A command handler for XMPP bots. + + Implements a command handler pattern. XMPP messages are processed by calling + message_received. Message objects handled by this class are annotated with + 'command' and 'arg' fields. On receipt of a message starting with a forward + or backward slash, the handler calls a method named after the command - eg, + if the user sends "/foo bar", the handler will call foo_command(message). + If no handler method matches, unhandled_command is called. The default behaviour + of unhandled_command is to send the message "Unknown command" back to + the sender. + + If the user sends a message not prefixed with a slash, + text_message(message) is called. + """ + + def unhandled_command(self, message): + """Called when an unknown command is sent to the XMPP bot. + + Args: + message: Message: The message that was sent by the user. + """ + message.reply('Unknown command') + + def text_message(self, message): + """Called when a message not prefixed by a /command is sent to the XMPP bot. + + Args: + message: Message: The message that was sent by the user. + """ + pass + + def message_received(self, message): + """Called when a message is sent to the XMPP bot. + + Args: + message: Message: The message that was sent by the user. + """ + if message.command: + handler_name = '%s_command' % (message.command,) + handler = getattr(self, handler_name, None) + if handler: + handler(message) + else: + self.unhandled_command(message) + else: + self.text_message(message) + + +class CommandHandler(CommandHandlerMixin, BaseHandler): + """A webapp implementation of CommandHandlerMixin.""" + pass diff --git a/google_appengine/google/appengine/ext/zipserve/__init__.py b/google_appengine/google/appengine/ext/zipserve/__init__.py new file mode 100755 index 0000000..2da833e --- /dev/null +++ b/google_appengine/google/appengine/ext/zipserve/__init__.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Serve static files from a zipfile. + +This is a solution for apps that want to serve 1000s of small static +files while staying withing the 1000 file limit. + +The simplest use case is driven purely from the handlers section in +app.yaml, e.g.: + + - url: /images/.* + script: $PYTHON_LIB/google/appengine/ext/zipserve + +This would invoke a main() within zipserve/__init__.py. This code +would then take the URL path, and look for a .zip file under the first +component of the path, in this case "images.zip" in the app's working +directory. If found, it will then serve any matching paths below that +from the zip file. In other words, /images/foo/icon.gif would map to +foo/icon.gif in the zip file images.zip. + +You can also customize the behavior by adding a custom line to your +WSGIApplication() invocation: + + def main(): + app = webapp.WSGIApplication( + [('/', MainPage), + ('/static/(.*)', zipserve.make_zip_handler('staticfiles.zip')), + ]) + +You can pass max_age=N to the make_zip_handler() call to override the +expiration time in seconds, which defaults to 600. + +To customize the behavior even more, you can subclass ZipHandler and +override the get() method, or override it and call ServeFromZipFile() +directly. + +Note that by default, a Cache-control is added that makes these pages +cacheable even if they require authentication. If this is not what +you want, override ZipHandler.SetCachingHeaders(). +""" + + +import email.Utils +import logging +import mimetypes +import time +import zipfile + +from google.appengine.ext import webapp +from google.appengine.ext.webapp import util + + +def make_zip_handler(zipfilename, max_age=None, public=None): + """Factory function to construct a custom ZipHandler instance. + + Args: + zipfilename: The filename of a zipfile. + max_age: Optional expiration time; defaults to ZipHandler.MAX_AGE. + public: Optional public flag; defaults to ZipHandler.PUBLIC. + + Returns: + A ZipHandler subclass. + """ + class CustomZipHandler(ZipHandler): + def get(self, name): + self.ServeFromZipFile(self.ZIPFILENAME, name) + ZIPFILENAME = zipfilename + if max_age is not None: + MAX_AGE = max_age + if public is not None: + PUBLIC = public + + return CustomZipHandler + + +class ZipHandler(webapp.RequestHandler): + """Request handler serving static files from zipfiles.""" + + zipfile_cache = {} + + def get(self, prefix, name): + """GET request handler. + + Typically the arguments are passed from the matching groups in the + URL pattern passed to WSGIApplication(). + + Args: + prefix: The zipfilename without the .zip suffix. + name: The name within the zipfile. + """ + self.ServeFromZipFile(prefix + '.zip', name) + + def ServeFromZipFile(self, zipfilename, name): + """Helper for the GET request handler. + + This serves the contents of file 'name' from zipfile + 'zipfilename', logging a message and returning a 404 response if + either the zipfile cannot be opened or the named file cannot be + read from it. + + Args: + zipfilename: The name of the zipfile. + name: The name within the zipfile. + """ + zipfile_object = self.zipfile_cache.get(zipfilename) + if zipfile_object is None: + try: + zipfile_object = zipfile.ZipFile(zipfilename) + except (IOError, RuntimeError), err: + logging.error('Can\'t open zipfile %s: %s', zipfilename, err) + zipfile_object = '' + self.zipfile_cache[zipfilename] = zipfile_object + if zipfile_object == '': + self.error(404) + self.response.out.write('Not found') + return + try: + data = zipfile_object.read(name) + except (KeyError, RuntimeError), err: + self.error(404) + self.response.out.write('Not found') + return + content_type, encoding = mimetypes.guess_type(name) + if content_type: + self.response.headers['Content-Type'] = content_type + self.SetCachingHeaders() + self.response.out.write(data) + + MAX_AGE = 600 + + PUBLIC = True + + def SetCachingHeaders(self): + """Helper to set the caching headers. + + Override this to customize the headers beyond setting MAX_AGE. + """ + max_age = self.MAX_AGE + self.response.headers['Expires'] = email.Utils.formatdate( + time.time() + max_age, usegmt=True) + cache_control = [] + if self.PUBLIC: + cache_control.append('public') + cache_control.append('max-age=%d' % max_age) + self.response.headers['Cache-Control'] = ', '.join(cache_control) + + +def main(): + """Main program. + + This is invoked when this package is referenced from app.yaml. + """ + application = webapp.WSGIApplication([('/([^/]+)/(.*)', ZipHandler)]) + util.run_wsgi_app(application) + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/runtime/__init__.py b/google_appengine/google/appengine/runtime/__init__.py new file mode 100755 index 0000000..939a073 --- /dev/null +++ b/google_appengine/google/appengine/runtime/__init__.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Define the DeadlineExceededError exception.""" + + + +try: + BaseException +except NameError: + BaseException = Exception + + +class DeadlineExceededError(BaseException): + """Exception raised when the request reaches its overall time limit. + + Not to be confused with runtime.apiproxy_errors.DeadlineExceededError. + That one is raised when individual API calls take too long. + """ diff --git a/google_appengine/google/appengine/runtime/__init__.pyc b/google_appengine/google/appengine/runtime/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c8a7fb7e82b8a9eabbf8c7ab53526d428e4fe54 GIT binary patch literal 772 zcwV)U!H&}~5QfJ|x0Imn0f__B9(;_1l)L~!fZ78BRj7*u^kSKK+87c~h@IW-nSH#y z@De-#Prx{ou2$MBmi${|kNnr66egI! z*9>S5f(NFs5#SOKpNxn-TLC74Yk(gBpEnaQDV*n%==O|dfm`cPh8pDpwQ0CmUR8(& z&9d*Qm&jEYEV&{)8T;>PI)VzGucP%T3qmA`xCyM!^LXZOSeE}BWv{JABQI-oBSDYf z2lRngUDfEN4PMd(_PS|gur12Qwl)-5CgqBP3?#pxtjKML(Z+^a_JfO^h3;(E)74&K z1oXwd+yxtbsCAIU5m}Ak!;BA;_W=;2Zb4nQckz1 zRLI7nB{MX!d@z=VDy8xm(XIHmEM-kCmaghGl?%Fb4Qb=c9np?&>#jq$W9q|3dAo6V zXQ`BRHYine`*k!ui^d7d^kkKv@VXue=kdpAkH_r8L*7|2@fgHd9naQxIsXgnnA!8c UDZO|cKM-@9PEs)wr{Yoi2f|#@$p8QV literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/runtime/apiproxy.py b/google_appengine/google/appengine/runtime/apiproxy.py new file mode 100755 index 0000000..037af17 --- /dev/null +++ b/google_appengine/google/appengine/runtime/apiproxy.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Makes API calls to various Google-provided services. + +Provides methods for making calls into Google Apphosting services and APIs +from your application code. This code will only work properly from within +the Google Apphosting environment. +""" + + +import sys +from google.net.proto import ProtocolBuffer +from google.appengine import runtime +from google.appengine.api import apiproxy_rpc +from google3.apphosting.runtime import _apphosting_runtime___python__apiproxy +from google.appengine.runtime import apiproxy_errors + +OK = 0 +RPC_FAILED = 1 +CALL_NOT_FOUND = 2 +ARGUMENT_ERROR = 3 +DEADLINE_EXCEEDED = 4 +CANCELLED = 5 +APPLICATION_ERROR = 6 +OTHER_ERROR = 7 +OVER_QUOTA = 8 +REQUEST_TOO_LARGE = 9 +CAPABILITY_DISABLED = 10 +FEATURE_DISABLED = 11 + +_ExceptionsMap = { + RPC_FAILED: + (apiproxy_errors.RPCFailedError, + "The remote RPC to the application server failed for the call %s.%s()."), + CALL_NOT_FOUND: + (apiproxy_errors.CallNotFoundError, + "The API package '%s' or call '%s()' was not found."), + ARGUMENT_ERROR: + (apiproxy_errors.ArgumentError, + "An error occurred parsing (locally or remotely) the arguments to %s.%s()."), + DEADLINE_EXCEEDED: + (apiproxy_errors.DeadlineExceededError, + "The API call %s.%s() took too long to respond and was cancelled."), + CANCELLED: + (apiproxy_errors.CancelledError, + "The API call %s.%s() was explicitly cancelled."), + OTHER_ERROR: + (apiproxy_errors.Error, + "An error occurred for the API request %s.%s()."), + OVER_QUOTA: + (apiproxy_errors.OverQuotaError, + "The API call %s.%s() required more quota than is available."), + REQUEST_TOO_LARGE: + (apiproxy_errors.RequestTooLargeError, + "The request to API call %s.%s() was too large."), + + + + + + + + + +} + +class RPC(apiproxy_rpc.RPC): + """A RPC object, suitable for talking to remote services. + + Each instance of this object can be used only once, and should not be reused. + + Stores the data members and methods for making RPC calls via the APIProxy. + """ + + def __init__(self, *args, **kargs): + """Constructor for the RPC object. All arguments are optional, and + simply set members on the class. These data members will be + overriden by values passed to MakeCall. + """ + super(RPC, self).__init__(*args, **kargs) + self.__result_dict = {} + + def _WaitImpl(self): + """Waits on the API call associated with this RPC. The callback, + if provided, will be executed before Wait() returns. If this RPC + is already complete, or if the RPC was never started, this + function will return immediately. + + Raises: + InterruptedError if a callback throws an uncaught exception. + """ + try: + rpc_completed = _apphosting_runtime___python__apiproxy.Wait(self) + except (runtime.DeadlineExceededError, apiproxy_errors.InterruptedError): + raise + except: + exc_class, exc, tb = sys.exc_info() + if (isinstance(exc, SystemError) and + exc.args[0] == 'uncaught RPC exception'): + raise + rpc = None + if hasattr(exc, "_appengine_apiproxy_rpc"): + rpc = exc._appengine_apiproxy_rpc + new_exc = apiproxy_errors.InterruptedError(exc, rpc) + raise new_exc.__class__, new_exc, tb + return True + + def _MakeCallImpl(self): + assert isinstance(self.request, ProtocolBuffer.ProtocolMessage), 'not isinstance(%r, %r): sys.modules=%r, sys.path=%r' % ( + self.request.__class__, + ProtocolBuffer.ProtocolMessage, + sys.modules, + sys.path) + assert isinstance(self.response, ProtocolBuffer.ProtocolMessage), 'not isinstance(%r, %r): sys.modules=%r, sys.path=%r' % ( + self.response.__class__, + ProtocolBuffer.ProtocolMessage, + sys.modules, + sys.path) + + e = ProtocolBuffer.Encoder() + self.request.Output(e) + + self.__state = RPC.RUNNING + + _apphosting_runtime___python__apiproxy.MakeCall( + self.package, self.call, e.buffer(), self.__result_dict, + self.__MakeCallDone, self, deadline=(self.deadline or -1)) + + def __MakeCallDone(self): + self.__state = RPC.FINISHING + self.cpu_usage_mcycles = self.__result_dict['cpu_usage_mcycles'] + if self.__result_dict['error'] == APPLICATION_ERROR: + self.__exception = apiproxy_errors.ApplicationError( + self.__result_dict['application_error'], + self.__result_dict['error_detail']) + elif self.__result_dict['error'] == CAPABILITY_DISABLED: + if self.__result_dict['error_detail']: + self.__exception = apiproxy_errors.CapabilityDisabledError( + self.__result_dict['error_detail']) + else: + self.__exception = apiproxy_errors.CapabilityDisabledError( + "The API call %s.%s() is temporarily unavailable." % ( + self.package, self.call)) + elif self.__result_dict['error'] == FEATURE_DISABLED: + self.__exception = apiproxy_errors.FeatureNotEnabledError( + self.__result_dict['error_detail']) + elif self.__result_dict['error'] in _ExceptionsMap: + exception_entry = _ExceptionsMap[self.__result_dict['error']] + self.__exception = exception_entry[0]( + exception_entry[1] % (self.package, self.call)) + else: + try: + self.response.ParseFromString(self.__result_dict['result_string']) + except Exception, e: + self.__exception = e + self.__Callback() + +def CreateRPC(): + """Create a RPC instance. suitable for talking to remote services. + + Each RPC instance can be used only once, and should not be reused. + + Returns: + an instance of RPC object + """ + return RPC() + + +def MakeSyncCall(package, call, request, response): + """Makes a synchronous (i.e. blocking) API call within the specified + package for the specified call method. request and response must be the + appropriately typed ProtocolBuffers for the API call. An exception is + thrown if an error occurs when communicating with the system. + + Args: + See MakeCall above. + + Raises: + See CheckSuccess() above. + """ + rpc = CreateRPC() + rpc.MakeCall(package, call, request, response) + rpc.Wait() + rpc.CheckSuccess() diff --git a/google_appengine/google/appengine/runtime/apiproxy.pyc b/google_appengine/google/appengine/runtime/apiproxy.pyc new file mode 100644 index 0000000000000000000000000000000000000000..800dd8fa668d33916636c5bcadb052b3b452f950 GIT binary patch literal 6741 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 J@uQ_v{{lIwsFnZ# literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/runtime/apiproxy_errors.py b/google_appengine/google/appengine/runtime/apiproxy_errors.py new file mode 100755 index 0000000..56ef68c --- /dev/null +++ b/google_appengine/google/appengine/runtime/apiproxy_errors.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Errors thrown by apiproxy.MakeSyncCall. +""" + + +class Error(Exception): + """Base APIProxy error type.""" + + +class RPCFailedError(Error): + """Raised by APIProxy calls when the RPC to the application server fails.""" + + +class CallNotFoundError(Error): + """Raised by APIProxy calls when the requested method cannot be found.""" + + +class ArgumentError(Error): + """Raised by APIProxy calls if there is an error parsing the arguments.""" + + +class DeadlineExceededError(Error): + """Raised by APIProxy calls if the call took too long to respond.""" + + +class CancelledError(Error): + """Raised by APIProxy calls if the call was cancelled, such as when + the user's request is exiting.""" + + +class ApplicationError(Error): + """Raised by APIProxy in the event of an application-level error.""" + def __init__(self, application_error, error_detail=''): + self.application_error = application_error + self.error_detail = error_detail + Error.__init__(self, application_error) + + def __str__(self): + return 'ApplicationError: %d %s' % (self.application_error, + self.error_detail) + +class OverQuotaError(Error): + """Raised by APIProxy calls when they have been blocked due to a lack of + available quota.""" + +class RequestTooLargeError(Error): + """Raised by APIProxy calls if the request was too large.""" + +class CapabilityDisabledError(Error): + """Raised by APIProxy when API calls are temporarily disabled.""" + +class FeatureNotEnabledError(Error): + """Raised by APIProxy when the app must enable a feature to use this call.""" + +class InterruptedError(Error): + """Raised by APIProxy.Wait() when the wait is interrupted by an uncaught + exception from some callback, not necessarily associated with the RPC in + question.""" + def __init__(self, exception, rpc): + self.args = ("The Wait() request was interrupted by an exception from " + "another callback:", exception) + self.__rpc = rpc + self.__exception = exception + + @property + def rpc(self): + return self.__rpc + + @property + def exception(self): + return self.__exception diff --git a/google_appengine/google/appengine/runtime/apiproxy_errors.pyc b/google_appengine/google/appengine/runtime/apiproxy_errors.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f651a26e2a3b5a3f38622701775775d8deed96d GIT binary patch literal 5263 zcwWU=+in{-5S^7|CBEkFTo4pQgS2Xq%IHJ$kQPB*Uy7iq>&PkE=4C-IDU0xGxmj}U z2*_Ld8U3mLL_eTk(3#nb6vs`i0i?wtP)O;mfsHJUC ziPbBD0$V6&BFHgm!cGJBke{gIHVNi+Lu)csK&>b+4CZOAz8F8H}>aE&n{3*V8R z6>KzO95B)i=~@`Z%#Gkm`}r9<8g_d<%;Y3P=mp-kAX83SP(`cC6pFc`_{~8)w2K z(=T)FJPsOaKd=g)CyDi9UyD9XI!iz(3OgbA zW!$7Pb0#?`yrrCUrtZ;3wI5HQaRV=#TB;b|+jo}$N!BAcQ9(~AvGel1c^nr zajBiHcTmRqWC9N|aziGOj;GHb@~l7{OGgn>O!7duJQ@gDgwulHVL7IgPhAyP=dneFR1)T>#q}?QzL!L$mDRKRqFPS3E=pzgt zVfz$c6S?<`7$3w%#C1JQ9uX?lk5ZVUJ$@a)Yvd=qY4A+|z5%95_xR{d0|H!Ov6nl% z98R7S6BStaC9;U_LZvfATcqQDZ;`B`w?6zHQFi;1^i!ZiFpuAe z3<@T}xDH%fq1^_!#UiT#t9u>Vv>kU7-rd(n;=U619gmo>hy8HnJS;0~3+J;id`Q!@ zN&`ET;7{4!%Kcc2m$Z^uW)R94k6@fZ$Ps%*j(cwHQ)EazB~TCF z2UMAJt1L=sm+6P*nI#OARvyO8yTT-@gZqvUC^29Gyb5dJLF;F(X9} zvM&xYjxn1hGS^V;<^(bnKTi^m1M<|L#H422uizcVC6>iwbE`T@Bm2&Aw^?=DH3$E3 zDsp- zb#`c3rxh$*LgLG*&U|4Vzj{u&hParrChvW_*$(F0ms&fWm1>FXlUtQfa?Q(sHt+$P x&y{?b;!^_GL(KNfb)4Zjnei;=V-TbHP4U;%W18FDV#8Kj^>=P{5!)tx{{dF47w-T7 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/__init__.py b/google_appengine/google/appengine/tools/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/appengine/tools/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/appengine/tools/__init__.pyc b/google_appengine/google/appengine/tools/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb139ba772ceb8ce7bcaf04d0785eb4200aa9e00 GIT binary patch literal 160 zcwW2siI?kyOo~r30~9a%knvjB+{28Lh_kcgiKNDhrC_5>388Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZarZgg{AteonD|e0*kJW=VX!UO{CE2hcQ| Q-29Z%oK!oIoy8zK0EYS}A^-pY literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/adaptive_thread_pool.py b/google_appengine/google/appengine/tools/adaptive_thread_pool.py new file mode 100755 index 0000000..25bcdc2 --- /dev/null +++ b/google_appengine/google/appengine/tools/adaptive_thread_pool.py @@ -0,0 +1,461 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Provides thread-pool-like functionality for workers accessing App Engine. + +The pool adapts to slow or timing out requests by reducing the number of +active workers, or increasing the number when requests latency reduces. +""" + + + +import logging +import Queue +import sys +import threading +import time +import traceback + +from google.appengine.tools.requeue import ReQueue + +logger = logging.getLogger('google.appengine.tools.adaptive_thread_pool') + +_THREAD_SHOULD_EXIT = '_THREAD_SHOULD_EXIT' + +INITIAL_BACKOFF = 1.0 + +BACKOFF_FACTOR = 2.0 + + +class Error(Exception): + """Base-class for exceptions in this module.""" + + +class WorkItemError(Error): + """Error while processing a WorkItem.""" + + +class RetryException(Error): + """A non-fatal exception that indicates that a work item should be retried.""" + + +def InterruptibleSleep(sleep_time): + """Puts thread to sleep, checking this threads exit_flag four times a second. + + Args: + sleep_time: Time to sleep. + """ + slept = 0.0 + epsilon = .0001 + thread = threading.currentThread() + while slept < sleep_time - epsilon: + remaining = sleep_time - slept + this_sleep_time = min(remaining, 0.25) + time.sleep(this_sleep_time) + slept += this_sleep_time + if thread.exit_flag: + return + + +class WorkerThread(threading.Thread): + """A WorkerThread to execute WorkItems. + + Attributes: + exit_flag: A boolean indicating whether this thread should stop + its work and exit. + """ + + def __init__(self, thread_pool, thread_gate, name=None): + """Initialize a WorkerThread instance. + + Args: + thread_pool: An AdaptiveThreadPool instance. + thread_gate: A ThreadGate instance. + name: A name for this WorkerThread. + """ + threading.Thread.__init__(self) + + self.setDaemon(True) + + self.exit_flag = False + self.__error = None + self.__traceback = None + self.__thread_pool = thread_pool + self.__work_queue = thread_pool.requeue + self.__thread_gate = thread_gate + if not name: + self.__name = 'Anonymous_' + self.__class__.__name__ + else: + self.__name = name + + def run(self): + """Perform the work of the thread.""" + logger.debug('[%s] %s: started', self.getName(), self.__class__.__name__) + + try: + self.WorkOnItems() + except: + self.SetError() + + logger.debug('[%s] %s: exiting', self.getName(), self.__class__.__name__) + + def SetError(self): + """Sets the error and traceback information for this thread. + + This must be called from an exception handler. + """ + if not self.__error: + exc_info = sys.exc_info() + self.__error = exc_info[1] + self.__traceback = exc_info[2] + logger.exception('[%s] %s:', self.getName(), self.__class__.__name__) + + def WorkOnItems(self): + """Perform the work of a WorkerThread.""" + while not self.exit_flag: + item = None + self.__thread_gate.StartWork() + try: + status, instruction = WorkItem.FAILURE, ThreadGate.DECREASE + try: + if self.exit_flag: + instruction = ThreadGate.HOLD + break + + try: + item = self.__work_queue.get(block=True, timeout=1.0) + except Queue.Empty: + instruction = ThreadGate.HOLD + continue + if item == _THREAD_SHOULD_EXIT or self.exit_flag: + status, instruction = WorkItem.SUCCESS, ThreadGate.HOLD + break + + logger.debug('[%s] Got work item %s', self.getName(), item) + + status, instruction = item.PerformWork(self.__thread_pool) + except RetryException: + status, instruction = WorkItem.RETRY, ThreadGate.HOLD + except: + self.SetError() + raise + + finally: + try: + if item: + if status == WorkItem.SUCCESS: + self.__work_queue.task_done() + elif status == WorkItem.RETRY: + try: + self.__work_queue.reput(item, block=False) + except Queue.Full: + logger.error('[%s] Failed to reput work item.', self.getName()) + raise Error('Failed to reput work item') + else: + if not self.__error: + if item.error: + self.__error = item.error + self.__traceback = item.traceback + else: + self.__error = WorkItemError( + 'Fatal error while processing %s' % item) + raise self.__error + + finally: + self.__thread_gate.FinishWork(instruction=instruction) + + def CheckError(self): + """If an error is present, then log it.""" + if self.__error: + logger.error('Error in %s: %s', self.getName(), self.__error) + if self.__traceback: + logger.debug('%s', ''.join(traceback.format_exception( + self.__error.__class__, + self.__error, + self.__traceback))) + + def __str__(self): + return self.__name + + +class AdaptiveThreadPool(object): + """A thread pool which processes WorkItems from a queue. + + Attributes: + requeue: The requeue instance which holds work items for this + thread pool. + """ + + def __init__(self, + num_threads, + queue_size=None, + base_thread_name=None, + worker_thread_factory=WorkerThread, + queue_factory=Queue.Queue): + """Initialize an AdaptiveThreadPool. + + An adaptive thread pool executes WorkItems using a number of + WorkerThreads. WorkItems represent items of work that may + succeed, soft fail, or hard fail. In addition, a completed work + item can signal this AdaptiveThreadPool to enable more or fewer + threads. Initially one thread is active. Soft failures are + reqeueud to be retried. Hard failures cause this + AdaptiveThreadPool to shut down entirely. See the WorkItem class + for more details. + + Args: + num_threads: The number of threads to use. + queue_size: The size of the work item queue to use. + base_thread_name: A string from which worker thread names are derived. + worker_thread_factory: A factory which procudes WorkerThreads. + queue_factory: Used for dependency injection. + """ + if queue_size is None: + queue_size = num_threads + self.requeue = ReQueue(queue_size, queue_factory=queue_factory) + self.__thread_gate = ThreadGate(num_threads) + self.__num_threads = num_threads + self.__threads = [] + for i in xrange(num_threads): + thread = worker_thread_factory(self, self.__thread_gate) + if base_thread_name: + base = base_thread_name + else: + base = thread.__class__.__name__ + thread.name = '%s-%d' % (base, i) + self.__threads.append(thread) + thread.start() + + def num_threads(self): + """Return the number of threads in this thread pool.""" + return self.__num_threads + + def Threads(self): + """Yields the registered threads.""" + for thread in self.__threads: + yield thread + + def SubmitItem(self, item, block=True, timeout=0.0): + """Submit a WorkItem to the AdaptiveThreadPool. + + Args: + item: A WorkItem instance. + block: Whether to block on submitting if the submit queue is full. + timeout: Time wait for room in the queue if block is True, 0.0 to + block indefinitely. + + Raises: + Queue.Full if the submit queue is full. + """ + self.requeue.put(item, block=block, timeout=timeout) + + def QueuedItemCount(self): + """Returns the number of items currently in the queue.""" + return self.requeue.qsize() + + def Shutdown(self): + """Shutdown the thread pool. + + Tasks may remain unexecuted in the submit queue. + """ + while not self.requeue.empty(): + try: + unused_item = self.requeue.get_nowait() + self.requeue.task_done() + except Queue.Empty: + pass + for thread in self.__threads: + thread.exit_flag = True + self.requeue.put(_THREAD_SHOULD_EXIT) + self.__thread_gate.EnableAllThreads() + + def Wait(self): + """Wait until all work items have been completed.""" + self.requeue.join() + + def JoinThreads(self): + """Wait for all threads to exit.""" + for thread in self.__threads: + logger.debug('Waiting for %s to exit' % str(thread)) + thread.join() + + def CheckErrors(self): + """Output logs for any errors that occurred in the worker threads.""" + for thread in self.__threads: + thread.CheckError() + + +class ThreadGate(object): + """Manage the number of active worker threads. + + The ThreadGate limits the number of threads that are simultaneously + active in order to implement adaptive rate control. + + Initially the ThreadGate allows only one thread to be active. For + each successful work item, another thread is activated and for each + failed item, the number of active threads is reduced by one. When only + one thread is active, failures will cause exponential backoff. + + For example, a ThreadGate instance, thread_gate can be used in a number + of threads as so: + + # Block until this thread is enabled for work. + thread_gate.StartWork() + try: + status = DoSomeWorkInvolvingLimitedSharedResources() + suceeded = IsStatusGood(status) + badly_failed = IsStatusVeryBad(status) + finally: + if suceeded: + # Suceeded, add more simultaneously enabled threads to the task. + thread_gate.FinishWork(instruction=ThreadGate.INCREASE) + elif badly_failed: + # Failed, or succeeded but with high resource load, reduce number of + # workers. + thread_gate.FinishWork(instruction=ThreadGate.DECREASE) + else: + # We succeeded, but don't want to add more workers to the task. + thread_gate.FinishWork(instruction=ThreadGate.HOLD) + + the thread_gate will enable and disable/backoff threads in response to + resource load conditions. + + StartWork can block indefinitely. FinishWork, while not + lock-free, should never block absent a demonic scheduler. + """ + + INCREASE = 'increase' + HOLD = 'hold' + DECREASE = 'decrease' + + def __init__(self, + num_threads, + sleep=InterruptibleSleep): + """Constructor for ThreadGate instances. + + Args: + num_threads: The total number of threads using this gate. + sleep: Used for dependency injection. + """ + self.__enabled_count = 1 + self.__lock = threading.Lock() + self.__thread_semaphore = threading.Semaphore(self.__enabled_count) + self.__num_threads = num_threads + self.__backoff_time = 0 + self.__sleep = sleep + + def num_threads(self): + return self.__num_threads + + def EnableThread(self): + """Enable one more worker thread.""" + self.__lock.acquire() + try: + self.__enabled_count += 1 + finally: + self.__lock.release() + self.__thread_semaphore.release() + + def EnableAllThreads(self): + """Enable all worker threads.""" + for unused_idx in xrange(self.__num_threads - self.__enabled_count): + self.EnableThread() + + def StartWork(self): + """Starts a critical section in which the number of workers is limited. + + Starts a critical section which allows self.__enabled_count + simultaneously operating threads. The critical section is ended by + calling self.FinishWork(). + """ + self.__thread_semaphore.acquire() + if self.__backoff_time > 0.0: + if not threading.currentThread().exit_flag: + logger.info('[%s] Backing off due to errors: %.1f seconds', + threading.currentThread().getName(), + self.__backoff_time) + self.__sleep(self.__backoff_time) + + def FinishWork(self, instruction=None): + """Ends a critical section started with self.StartWork().""" + if not instruction or instruction == ThreadGate.HOLD: + self.__thread_semaphore.release() + + elif instruction == ThreadGate.INCREASE: + if self.__backoff_time > 0.0: + logger.info('Resetting backoff to 0.0') + self.__backoff_time = 0.0 + do_enable = False + self.__lock.acquire() + try: + if self.__num_threads > self.__enabled_count: + do_enable = True + self.__enabled_count += 1 + finally: + self.__lock.release() + if do_enable: + logger.debug('Increasing active thread count to %d', + self.__enabled_count) + self.__thread_semaphore.release() + self.__thread_semaphore.release() + + elif instruction == ThreadGate.DECREASE: + do_disable = False + self.__lock.acquire() + try: + if self.__enabled_count > 1: + do_disable = True + self.__enabled_count -= 1 + else: + if self.__backoff_time == 0.0: + self.__backoff_time = INITIAL_BACKOFF + else: + self.__backoff_time *= BACKOFF_FACTOR + finally: + self.__lock.release() + if do_disable: + logger.debug('Decreasing the number of active threads to %d', + self.__enabled_count) + else: + self.__thread_semaphore.release() + + +class WorkItem(object): + """Holds a unit of work.""" + + SUCCESS = 'success' + RETRY = 'retry' + FAILURE = 'failure' + + def __init__(self, name): + self.__name = name + + def PerformWork(self, thread_pool): + """Perform the work of this work item and report the results. + + Args: + thread_pool: The AdaptiveThreadPool instance associated with this + thread. + + Returns: + A tuple (status, instruction) of the work status and an instruction + for the ThreadGate. + """ + raise NotImplementedError + + def __str__(self): + return self.__name diff --git a/google_appengine/google/appengine/tools/adaptive_thread_pool.pyc b/google_appengine/google/appengine/tools/adaptive_thread_pool.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd363b6f5669ddcae4a6b4c2c21a022e82feef8f GIT binary patch literal 17418 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/appcfg.py b/google_appengine/google/appengine/tools/appcfg.py new file mode 100755 index 0000000..d80462e --- /dev/null +++ b/google_appengine/google/appengine/tools/appcfg.py @@ -0,0 +1,2875 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Tool for deploying apps to an app server. + +Currently, the application only uploads new appversions. To do this, it first +walks the directory tree rooted at the path the user specifies, adding all the +files it finds to a list. It then uploads the application configuration +(app.yaml) to the server using HTTP, followed by uploading each of the files. +It then commits the transaction with another request. + +The bulk of this work is handled by the AppVersionUpload class, which exposes +methods to add to the list of files, fetch a list of modified files, upload +files, and commit or rollback the transaction. +""" + + +import calendar +import datetime +import getpass +import logging +import mimetypes +import optparse +import os +import random +import re +import sha +import sys +import tempfile +import time +import urllib +import urllib2 + +import google +import yaml +from google.appengine.cron import groctimespecification +from google.appengine.api import appinfo +from google.appengine.api import croninfo +from google.appengine.api import dosinfo +from google.appengine.api import queueinfo +from google.appengine.api import validation +from google.appengine.api import yaml_errors +from google.appengine.api import yaml_object +from google.appengine.datastore import datastore_index +from google.appengine.tools import appengine_rpc +from google.appengine.tools import bulkloader + + +MAX_FILES_TO_CLONE = 100 +LIST_DELIMITER = '\n' +TUPLE_DELIMITER = '|' + +VERSION_FILE = '../VERSION' + +UPDATE_CHECK_TIMEOUT = 3 + +NAG_FILE = '.appcfg_nag' + +MAX_LOG_LEVEL = 4 + +MAX_BATCH_SIZE = 1000000 +MAX_BATCH_COUNT = 100 +MAX_BATCH_FILE_SIZE = 200000 +BATCH_OVERHEAD = 500 + +verbosity = 1 + + +appinfo.AppInfoExternal.ATTRIBUTES[appinfo.RUNTIME] = 'python' +_api_versions = os.environ.get('GOOGLE_TEST_API_VERSIONS', '1') +_options = validation.Options(*_api_versions.split(',')) +appinfo.AppInfoExternal.ATTRIBUTES[appinfo.API_VERSION] = _options +del _api_versions, _options + + +def StatusUpdate(msg): + """Print a status message to stderr. + + If 'verbosity' is greater than 0, print the message. + + Args: + msg: The string to print. + """ + if verbosity > 0: + print >>sys.stderr, msg + + +def GetMimeTypeIfStaticFile(config, filename): + """Looks up the mime type for 'filename'. + + Uses the handlers in 'config' to determine if the file should + be treated as a static file. + + Args: + config: The app.yaml object to check the filename against. + filename: The name of the file. + + Returns: + The mime type string. For example, 'text/plain' or 'image/gif'. + None if this is not a static file. + """ + for handler in config.handlers: + handler_type = handler.GetHandlerType() + if handler_type in ('static_dir', 'static_files'): + if handler_type == 'static_dir': + regex = os.path.join(re.escape(handler.GetHandler()), '.*') + else: + regex = handler.upload + if re.match(regex, filename): + if handler.mime_type is not None: + return handler.mime_type + else: + guess = mimetypes.guess_type(filename)[0] + if guess is None: + default = 'application/octet-stream' + print >>sys.stderr, ('Could not guess mimetype for %s. Using %s.' + % (filename, default)) + return default + return guess + return None + + +def LookupErrorBlob(config, filename): + """Looks up the mime type and error_code for 'filename'. + + Uses the error handlers in 'config' to determine if the file should + be treated as an error blob. + + Args: + config: The app.yaml object to check the filename against. + filename: The name of the file. + + Returns: + + A tuple of (mime_type, error_code), or (None, None) if this is not an error + blob. For example, ('text/plain', default) or ('image/gif', timeout) or + (None, None). + """ + if not config.error_handlers: + return (None, None) + for error_handler in config.error_handlers: + if error_handler.file == filename: + error_code = error_handler.error_code + if not error_code: + error_code = 'default' + if error_handler.mime_type is not None: + return (error_handler.mime_type, error_code) + else: + guess = mimetypes.guess_type(filename)[0] + if guess is None: + default = 'application/octet-stream' + print >>sys.stderr, ('Could not guess mimetype for %s. Using %s.' + % (filename, default)) + return (default, error_code) + return (guess, error_code) + return (None, None) + + +def BuildClonePostBody(file_tuples): + """Build the post body for the /api/clone{files,blobs,errorblobs} urls. + + Args: + file_tuples: A list of tuples. Each tuple should contain the entries + appropriate for the endpoint in question. + + Returns: + A string containing the properly delimited tuples. + """ + file_list = [] + for tup in file_tuples: + path = tup[0] + tup = tup[1:] + file_list.append(TUPLE_DELIMITER.join([path] + list(tup))) + return LIST_DELIMITER.join(file_list) + + +class NagFile(validation.Validated): + """A validated YAML class to represent the user's nag preferences. + + Attributes: + timestamp: The timestamp of the last nag. + opt_in: True if the user wants to check for updates on dev_appserver + start. False if not. May be None if we have not asked the user yet. + """ + + ATTRIBUTES = { + 'timestamp': validation.TYPE_FLOAT, + 'opt_in': validation.Optional(validation.TYPE_BOOL), + } + + @staticmethod + def Load(nag_file): + """Load a single NagFile object where one and only one is expected. + + Args: + nag_file: A file-like object or string containing the yaml data to parse. + + Returns: + A NagFile instance. + """ + return yaml_object.BuildSingleObject(NagFile, nag_file) + + +def GetVersionObject(isfile=os.path.isfile, open_fn=open): + """Gets the version of the SDK by parsing the VERSION file. + + Args: + isfile: used for testing. + open_fn: Used for testing. + + Returns: + A Yaml object or None if the VERSION file does not exist. + """ + version_filename = os.path.join(os.path.dirname(google.__file__), + VERSION_FILE) + if not isfile(version_filename): + logging.error('Could not find version file at %s', version_filename) + return None + + version_fh = open_fn(version_filename, 'r') + try: + version = yaml.safe_load(version_fh) + finally: + version_fh.close() + + return version + + +def RetryWithBackoff(initial_delay, backoff_factor, max_delay, max_tries, + callable_func): + """Calls a function multiple times, backing off more and more each time. + + Args: + initial_delay: Initial delay after first try, in seconds. + backoff_factor: Delay will be multiplied by this factor after each try. + max_delay: Max delay factor. + max_tries: Maximum number of tries. + callable_func: The method to call, will pass no arguments. + + Returns: + True if the function succeded in one of its tries. + + Raises: + Whatever the function raises--an exception will immediately stop retries. + """ + delay = initial_delay + if callable_func(): + return True + while max_tries > 1: + StatusUpdate('Will check again in %s seconds.' % delay) + time.sleep(delay) + delay *= backoff_factor + if max_delay and delay > max_delay: + delay = max_delay + max_tries -= 1 + if callable_func(): + return True + return False + + +def _VersionList(release): + """Parse a version string into a list of ints. + + Args: + release: The 'release' version, e.g. '1.2.4'. + (Due to YAML parsing this may also be an int or float.) + + Returns: + A list of ints corresponding to the parts of the version string + between periods. Example: + '1.2.4' -> [1, 2, 4] + '1.2.3.4' -> [1, 2, 3, 4] + + Raises: + ValueError if not all the parts are valid integers. + """ + return [int(part) for part in str(release).split('.')] + + +class UpdateCheck(object): + """Determines if the local SDK is the latest version. + + Nags the user when there are updates to the SDK. As the SDK becomes + more out of date, the language in the nagging gets stronger. We + store a little yaml file in the user's home directory so that we nag + the user only once a week. + + The yaml file has the following field: + 'timestamp': Last time we nagged the user in seconds since the epoch. + + Attributes: + server: An AbstractRpcServer instance used to check for the latest SDK. + config: The app's AppInfoExternal. Needed to determine which api_version + the app is using. + """ + + def __init__(self, + server, + config, + isdir=os.path.isdir, + isfile=os.path.isfile, + open_fn=open): + """Create a new UpdateCheck. + + Args: + server: The AbstractRpcServer to use. + config: The yaml object that specifies the configuration of this + application. + isdir: Replacement for os.path.isdir (for testing). + isfile: Replacement for os.path.isfile (for testing). + open_fn: Replacement for the open builtin (for testing). + """ + self.server = server + self.config = config + self.isdir = isdir + self.isfile = isfile + self.open = open_fn + + @staticmethod + def MakeNagFilename(): + """Returns the filename for the nag file for this user.""" + user_homedir = os.path.expanduser('~/') + if not os.path.isdir(user_homedir): + drive, unused_tail = os.path.splitdrive(os.__file__) + if drive: + os.environ['HOMEDRIVE'] = drive + + return os.path.expanduser('~/' + NAG_FILE) + + def _ParseVersionFile(self): + """Parse the local VERSION file. + + Returns: + A Yaml object or None if the file does not exist. + """ + return GetVersionObject(isfile=self.isfile, open_fn=self.open) + + def CheckSupportedVersion(self): + """Determines if the app's api_version is supported by the SDK. + + Uses the api_version field from the AppInfoExternal to determine if + the SDK supports that api_version. + + Raises: + SystemExit if the api_version is not supported. + """ + version = self._ParseVersionFile() + if version is None: + logging.error('Could not determine if the SDK supports the api_version ' + 'requested in app.yaml.') + return + if self.config.api_version not in version['api_versions']: + logging.critical('The api_version specified in app.yaml (%s) is not ' + 'supported by this release of the SDK. The supported ' + 'api_versions are %s.', + self.config.api_version, version['api_versions']) + sys.exit(1) + + def CheckForUpdates(self): + """Queries the server for updates and nags the user if appropriate. + + Queries the server for the latest SDK version at the same time reporting + the local SDK version. The server will respond with a yaml document + containing the fields: + 'release': The name of the release (e.g. 1.2). + 'timestamp': The time the release was created (YYYY-MM-DD HH:MM AM/PM TZ). + 'api_versions': A list of api_version strings (e.g. ['1', 'beta']). + + We will nag the user with increasing severity if: + - There is a new release. + - There is a new release with a new api_version. + - There is a new release that does not support the api_version named in + self.config. + """ + version = self._ParseVersionFile() + if version is None: + logging.info('Skipping update check') + return + logging.info('Checking for updates to the SDK.') + + try: + response = self.server.Send('/api/updatecheck', + timeout=UPDATE_CHECK_TIMEOUT, + release=version['release'], + timestamp=version['timestamp'], + api_versions=version['api_versions']) + except urllib2.URLError, e: + logging.info('Update check failed: %s', e) + return + + latest = yaml.safe_load(response) + if version['release'] == latest['release']: + logging.info('The SDK is up to date.') + return + + try: + this_release = _VersionList(version['release']) + except ValueError: + logging.warn('Could not parse this release version (%r)', + version['release']) + else: + try: + advertised_release = _VersionList(latest['release']) + except ValueError: + logging.warn('Could not parse advertised release version (%r)', + latest['release']) + else: + if this_release > advertised_release: + logging.info('This SDK release is newer than the advertised release.') + return + + api_versions = latest['api_versions'] + if self.config.api_version not in api_versions: + self._Nag( + 'The api version you are using (%s) is obsolete! You should\n' + 'upgrade your SDK and test that your code works with the new\n' + 'api version.' % self.config.api_version, + latest, version, force=True) + return + + if self.config.api_version != api_versions[len(api_versions) - 1]: + self._Nag( + 'The api version you are using (%s) is deprecated. You should\n' + 'upgrade your SDK to try the new functionality.' % + self.config.api_version, latest, version) + return + + self._Nag('There is a new release of the SDK available.', + latest, version) + + def _ParseNagFile(self): + """Parses the nag file. + + Returns: + A NagFile if the file was present else None. + """ + nag_filename = UpdateCheck.MakeNagFilename() + if self.isfile(nag_filename): + fh = self.open(nag_filename, 'r') + try: + nag = NagFile.Load(fh) + finally: + fh.close() + return nag + return None + + def _WriteNagFile(self, nag): + """Writes the NagFile to the user's nag file. + + If the destination path does not exist, this method will log an error + and fail silently. + + Args: + nag: The NagFile to write. + """ + nagfilename = UpdateCheck.MakeNagFilename() + try: + fh = self.open(nagfilename, 'w') + try: + fh.write(nag.ToYAML()) + finally: + fh.close() + except (OSError, IOError), e: + logging.error('Could not write nag file to %s. Error: %s', nagfilename, e) + + def _Nag(self, msg, latest, version, force=False): + """Prints a nag message and updates the nag file's timestamp. + + Because we don't want to nag the user everytime, we store a simple + yaml document in the user's home directory. If the timestamp in this + doc is over a week old, we'll nag the user. And when we nag the user, + we update the timestamp in this doc. + + Args: + msg: The formatted message to print to the user. + latest: The yaml document received from the server. + version: The local yaml version document. + force: If True, always nag the user, ignoring the nag file. + """ + nag = self._ParseNagFile() + if nag and not force: + last_nag = datetime.datetime.fromtimestamp(nag.timestamp) + if datetime.datetime.now() - last_nag < datetime.timedelta(weeks=1): + logging.debug('Skipping nag message') + return + + if nag is None: + nag = NagFile() + nag.timestamp = time.time() + self._WriteNagFile(nag) + + print '****************************************************************' + print msg + print '-----------' + print 'Latest SDK:' + print yaml.dump(latest) + print '-----------' + print 'Your SDK:' + print yaml.dump(version) + print '-----------' + print 'Please visit http://code.google.com/appengine for the latest SDK' + print '****************************************************************' + + def AllowedToCheckForUpdates(self, input_fn=raw_input): + """Determines if the user wants to check for updates. + + On startup, the dev_appserver wants to check for updates to the SDK. + Because this action reports usage to Google when the user is not + otherwise communicating with Google (e.g. pushing a new app version), + the user must opt in. + + If the user does not have a nag file, we will query the user and + save the response in the nag file. Subsequent calls to this function + will re-use that response. + + Args: + input_fn: used to collect user input. This is for testing only. + + Returns: + True if the user wants to check for updates. False otherwise. + """ + nag = self._ParseNagFile() + if nag is None: + nag = NagFile() + nag.timestamp = time.time() + + if nag.opt_in is None: + answer = input_fn('Allow dev_appserver to check for updates on startup? ' + '(Y/n): ') + answer = answer.strip().lower() + if answer == 'n' or answer == 'no': + print ('dev_appserver will not check for updates on startup. To ' + 'change this setting, edit %s' % UpdateCheck.MakeNagFilename()) + nag.opt_in = False + else: + print ('dev_appserver will check for updates on startup. To change ' + 'this setting, edit %s' % UpdateCheck.MakeNagFilename()) + nag.opt_in = True + self._WriteNagFile(nag) + return nag.opt_in + + +class IndexDefinitionUpload(object): + """Provides facilities to upload index definitions to the hosting service.""" + + def __init__(self, server, config, definitions): + """Creates a new DatastoreIndexUpload. + + Args: + server: The RPC server to use. Should be an instance of HttpRpcServer + or TestRpcServer. + config: The AppInfoExternal object derived from the app.yaml file. + definitions: An IndexDefinitions object. + """ + self.server = server + self.config = config + self.definitions = definitions + + def DoUpload(self): + """Uploads the index definitions.""" + StatusUpdate('Uploading index definitions.') + self.server.Send('/api/datastore/index/add', + app_id=self.config.application, + version=self.config.version, + payload=self.definitions.ToYAML()) + + +class CronEntryUpload(object): + """Provides facilities to upload cron entries to the hosting service.""" + + def __init__(self, server, config, cron): + """Creates a new CronEntryUpload. + + Args: + server: The RPC server to use. Should be an instance of a subclass of + AbstractRpcServer + config: The AppInfoExternal object derived from the app.yaml file. + cron: The CronInfoExternal object loaded from the cron.yaml file. + """ + self.server = server + self.config = config + self.cron = cron + + def DoUpload(self): + """Uploads the cron entries.""" + StatusUpdate('Uploading cron entries.') + self.server.Send('/api/cron/update', + app_id=self.config.application, + version=self.config.version, + payload=self.cron.ToYAML()) + + +class QueueEntryUpload(object): + """Provides facilities to upload task queue entries to the hosting service.""" + + def __init__(self, server, config, queue): + """Creates a new QueueEntryUpload. + + Args: + server: The RPC server to use. Should be an instance of a subclass of + AbstractRpcServer + config: The AppInfoExternal object derived from the app.yaml file. + queue: The QueueInfoExternal object loaded from the queue.yaml file. + """ + self.server = server + self.config = config + self.queue = queue + + def DoUpload(self): + """Uploads the task queue entries.""" + StatusUpdate('Uploading task queue entries.') + self.server.Send('/api/queue/update', + app_id=self.config.application, + version=self.config.version, + payload=self.queue.ToYAML()) + + +class DosEntryUpload(object): + """Provides facilities to upload dos entries to the hosting service.""" + + def __init__(self, server, config, dos): + """Creates a new DosEntryUpload. + + Args: + server: The RPC server to use. Should be an instance of a subclass of + AbstractRpcServer. + config: The AppInfoExternal object derived from the app.yaml file. + dos: The DosInfoExternal object loaded from the dos.yaml file. + """ + self.server = server + self.config = config + self.dos = dos + + def DoUpload(self): + """Uploads the dos entries.""" + StatusUpdate('Uploading DOS entries.') + self.server.Send('/api/dos/update', + app_id=self.config.application, + version=self.config.version, + payload=self.dos.ToYAML()) + + +class DefaultVersionSet(object): + """Provides facilities to set the default (serving) version.""" + + def __init__(self, server, config): + """Creates a new DefaultVersionSet. + + Args: + server: The RPC server to use. Should be an instance of a subclass of + AbstractRpcServer. + config: The AppInfoExternal object derived from the app.yaml file. + """ + self.server = server + self.config = config + + def SetVersion(self): + """Sets the default version.""" + StatusUpdate('Setting default version to %s.' % (self.config.version,)) + self.server.Send('/api/appversion/setdefault', + app_id=self.config.application, + version=self.config.version) + + +class IndexOperation(object): + """Provide facilities for writing Index operation commands.""" + + def __init__(self, server, config): + """Creates a new IndexOperation. + + Args: + server: The RPC server to use. Should be an instance of HttpRpcServer + or TestRpcServer. + config: appinfo.AppInfoExternal configuration object. + """ + self.server = server + self.config = config + + def DoDiff(self, definitions): + """Retrieve diff file from the server. + + Args: + definitions: datastore_index.IndexDefinitions as loaded from users + index.yaml file. + + Returns: + A pair of datastore_index.IndexDefinitions objects. The first record + is the set of indexes that are present in the index.yaml file but missing + from the server. The second record is the set of indexes that are + present on the server but missing from the index.yaml file (indicating + that these indexes should probably be vacuumed). + """ + StatusUpdate('Fetching index definitions diff.') + response = self.server.Send('/api/datastore/index/diff', + app_id=self.config.application, + payload=definitions.ToYAML()) + return datastore_index.ParseMultipleIndexDefinitions(response) + + def DoDelete(self, definitions): + """Delete indexes from the server. + + Args: + definitions: Index definitions to delete from datastore. + + Returns: + A single datstore_index.IndexDefinitions containing indexes that were + not deleted, probably because they were already removed. This may + be normal behavior as there is a potential race condition between fetching + the index-diff and sending deletion confirmation through. + """ + StatusUpdate('Deleting selected index definitions.') + response = self.server.Send('/api/datastore/index/delete', + app_id=self.config.application, + payload=definitions.ToYAML()) + return datastore_index.ParseIndexDefinitions(response) + + +class VacuumIndexesOperation(IndexOperation): + """Provide facilities to request the deletion of datastore indexes.""" + + def __init__(self, server, config, force, + confirmation_fn=raw_input): + """Creates a new VacuumIndexesOperation. + + Args: + server: The RPC server to use. Should be an instance of HttpRpcServer + or TestRpcServer. + config: appinfo.AppInfoExternal configuration object. + force: True to force deletion of indexes, else False. + confirmation_fn: Function used for getting input form user. + """ + super(VacuumIndexesOperation, self).__init__(server, config) + self.force = force + self.confirmation_fn = confirmation_fn + + def GetConfirmation(self, index): + """Get confirmation from user to delete an index. + + This method will enter an input loop until the user provides a + response it is expecting. Valid input is one of three responses: + + y: Confirm deletion of index. + n: Do not delete index. + a: Delete all indexes without asking for further confirmation. + + If the user enters nothing at all, the default action is to skip + that index and do not delete. + + If the user selects 'a', as a side effect, the 'force' flag is set. + + Args: + index: Index to confirm. + + Returns: + True if user enters 'y' or 'a'. False if user enter 'n'. + """ + while True: + print 'This index is no longer defined in your index.yaml file.' + print + print index.ToYAML() + print + + confirmation = self.confirmation_fn( + 'Are you sure you want to delete this index? (N/y/a): ') + confirmation = confirmation.strip().lower() + + if confirmation == 'y': + return True + elif confirmation == 'n' or not confirmation: + return False + elif confirmation == 'a': + self.force = True + return True + else: + print 'Did not understand your response.' + + def DoVacuum(self, definitions): + """Vacuum indexes in datastore. + + This method will query the server to determine which indexes are not + being used according to the user's local index.yaml file. Once it has + made this determination, it confirms with the user which unused indexes + should be deleted. Once confirmation for each index is receives, it + deletes those indexes. + + Because another user may in theory delete the same indexes at the same + time as the user, there is a potential race condition. In this rare cases, + some of the indexes previously confirmed for deletion will not be found. + The user is notified which indexes these were. + + Args: + definitions: datastore_index.IndexDefinitions as loaded from users + index.yaml file. + """ + unused_new_indexes, notused_indexes = self.DoDiff(definitions) + + deletions = datastore_index.IndexDefinitions(indexes=[]) + if notused_indexes.indexes is not None: + for index in notused_indexes.indexes: + if self.force or self.GetConfirmation(index): + deletions.indexes.append(index) + + if deletions.indexes: + not_deleted = self.DoDelete(deletions) + + if not_deleted.indexes: + not_deleted_count = len(not_deleted.indexes) + if not_deleted_count == 1: + warning_message = ('An index was not deleted. Most likely this is ' + 'because it no longer exists.\n\n') + else: + warning_message = ('%d indexes were not deleted. Most likely this ' + 'is because they no longer exist.\n\n' + % not_deleted_count) + for index in not_deleted.indexes: + warning_message += index.ToYAML() + logging.warning(warning_message) + + +class LogsRequester(object): + """Provide facilities to export request logs.""" + + def __init__(self, server, config, output_file, + num_days, append, severity, end, vhost, include_vhost, + include_all=None, time_func=time.time): + """Constructor. + + Args: + server: The RPC server to use. Should be an instance of HttpRpcServer + or TestRpcServer. + config: appinfo.AppInfoExternal configuration object. + output_file: Output file name. + num_days: Number of days worth of logs to export; 0 for all available. + append: True if appending to an existing file. + severity: App log severity to request (0-4); None for no app logs. + end: date object representing last day of logs to return. + vhost: The virtual host of log messages to get. None for all hosts. + include_vhost: If true, the virtual host is included in log messages. + include_all: If true, we add to the log message everything we know + about the request. + time_func: Method that return a timestamp representing now (for testing). + """ + self.server = server + self.config = config + self.output_file = output_file + self.append = append + self.num_days = num_days + self.severity = severity + self.vhost = vhost + self.include_vhost = include_vhost + self.include_all = include_all + self.version_id = self.config.version + '.1' + self.sentinel = None + self.write_mode = 'w' + if self.append: + self.sentinel = FindSentinel(self.output_file) + self.write_mode = 'a' + + self.skip_until = False + now = PacificDate(time_func()) + if end < now: + self.skip_until = end + else: + end = now + + self.valid_dates = None + if self.num_days: + start = end - datetime.timedelta(self.num_days - 1) + self.valid_dates = (start, end) + + def DownloadLogs(self): + """Download the requested logs. + + This will write the logs to the file designated by + self.output_file, or to stdout if the filename is '-'. + Multiple roundtrips to the server may be made. + """ + StatusUpdate('Downloading request logs for %s %s.' % + (self.config.application, self.version_id)) + tf = tempfile.TemporaryFile() + last_offset = None + try: + while True: + try: + new_offset = self.RequestLogLines(tf, last_offset) + if not new_offset or new_offset == last_offset: + break + last_offset = new_offset + except KeyboardInterrupt: + StatusUpdate('Keyboard interrupt; saving data downloaded so far.') + break + StatusUpdate('Copying request logs to %r.' % self.output_file) + if self.output_file == '-': + of = sys.stdout + else: + try: + of = open(self.output_file, self.write_mode) + except IOError, err: + StatusUpdate('Can\'t write %r: %s.' % (self.output_file, err)) + sys.exit(1) + try: + line_count = CopyReversedLines(tf, of) + finally: + of.flush() + if of is not sys.stdout: + of.close() + finally: + tf.close() + StatusUpdate('Copied %d records.' % line_count) + + def RequestLogLines(self, tf, offset): + """Make a single roundtrip to the server. + + Args: + tf: Writable binary stream to which the log lines returned by + the server are written, stripped of headers, and excluding + lines skipped due to self.sentinel or self.valid_dates filtering. + offset: Offset string for a continued request; None for the first. + + Returns: + The offset string to be used for the next request, if another + request should be issued; or None, if not. + """ + logging.info('Request with offset %r.', offset) + kwds = {'app_id': self.config.application, + 'version': self.version_id, + 'limit': 1000, + } + if offset: + kwds['offset'] = offset + if self.severity is not None: + kwds['severity'] = str(self.severity) + if self.vhost is not None: + kwds['vhost'] = str(self.vhost) + if self.include_vhost is not None: + kwds['include_vhost'] = str(self.include_vhost) + if self.include_all is not None: + kwds['include_all'] = str(self.include_all) + response = self.server.Send('/api/request_logs', payload=None, **kwds) + response = response.replace('\r', '\0') + lines = response.splitlines() + logging.info('Received %d bytes, %d records.', len(response), len(lines)) + offset = None + if lines and lines[0].startswith('#'): + match = re.match(r'^#\s*next_offset=(\S+)\s*$', lines[0]) + del lines[0] + if match: + offset = match.group(1) + if lines and lines[-1].startswith('#'): + del lines[-1] + valid_dates = self.valid_dates + sentinel = self.sentinel + skip_until = self.skip_until + len_sentinel = None + if sentinel: + len_sentinel = len(sentinel) + for line in lines: + if (sentinel and + line.startswith(sentinel) and + line[len_sentinel : len_sentinel+1] in ('', '\0')): + return None + + linedate = DateOfLogLine(line) + if not linedate: + continue + + if skip_until: + if linedate > skip_until: + continue + else: + self.skip_until = skip_until = False + + if valid_dates and not valid_dates[0] <= linedate <= valid_dates[1]: + return None + tf.write(line + '\n') + if not lines: + return None + return offset + + +def DateOfLogLine(line): + """Returns a date object representing the log line's timestamp. + + Args: + line: a log line string. + Returns: + A date object representing the timestamp or None if parsing fails. + """ + m = re.compile(r'[^[]+\[(\d+/[A-Za-z]+/\d+):[^\d]*').match(line) + if not m: + return None + try: + return datetime.date(*time.strptime(m.group(1), '%d/%b/%Y')[:3]) + except ValueError: + return None + + +def PacificDate(now): + """For a UTC timestamp, return the date in the US/Pacific timezone. + + Args: + now: A posix timestamp giving current UTC time. + + Returns: + A date object representing what day it is in the US/Pacific timezone. + """ + return datetime.date(*time.gmtime(PacificTime(now))[:3]) + + +def PacificTime(now): + """Helper to return the number of seconds between UTC and Pacific time. + + This is needed to compute today's date in Pacific time (more + specifically: Mountain View local time), which is how request logs + are reported. (Google servers always report times in Mountain View + local time, regardless of where they are physically located.) + + This takes (post-2006) US DST into account. Pacific time is either + 8 hours or 7 hours west of UTC, depending on whether DST is in + effect. Since 2007, US DST starts on the Second Sunday in March + March, and ends on the first Sunday in November. (Reference: + http://aa.usno.navy.mil/faq/docs/daylight_time.php.) + + Note that the server doesn't report its local time (the HTTP Date + header uses UTC), and the client's local time is irrelevant. + + Args: + now: A posix timestamp giving current UTC time. + + Returns: + A pseudo-posix timestamp giving current Pacific time. Passing + this through time.gmtime() will produce a tuple in Pacific local + time. + """ + now -= 8*3600 + if IsPacificDST(now): + now += 3600 + return now + + +def IsPacificDST(now): + """Helper for PacificTime to decide whether now is Pacific DST (PDT). + + Args: + now: A pseudo-posix timestamp giving current time in PST. + + Returns: + True if now falls within the range of DST, False otherwise. + """ + DAY = 24*3600 + SUNDAY = 6 + pst = time.gmtime(now) + year = pst[0] + assert year >= 2007 + begin = calendar.timegm((year, 3, 8, 2, 0, 0, 0, 0, 0)) + while time.gmtime(begin).tm_wday != SUNDAY: + begin += DAY + end = calendar.timegm((year, 11, 1, 2, 0, 0, 0, 0, 0)) + while time.gmtime(end).tm_wday != SUNDAY: + end += DAY + return begin <= now < end + + +def CopyReversedLines(instream, outstream, blocksize=2**16): + r"""Copy lines from input stream to output stream in reverse order. + + As a special feature, null bytes in the input are turned into + newlines followed by tabs in the output, but these 'sub-lines' + separated by null bytes are not reversed. E.g. If the input is + 'A\0B\nC\0D\n', the output is 'C\n\tD\nA\n\tB\n'. + + Args: + instream: A seekable stream open for reading in binary mode. + outstream: A stream open for writing; doesn't have to be seekable or binary. + blocksize: Optional block size for buffering, for unit testing. + + Returns: + The number of lines copied. + """ + line_count = 0 + instream.seek(0, 2) + last_block = instream.tell() // blocksize + spillover = '' + for iblock in xrange(last_block + 1, -1, -1): + instream.seek(iblock * blocksize) + data = instream.read(blocksize) + lines = data.splitlines(True) + lines[-1:] = ''.join(lines[-1:] + [spillover]).splitlines(True) + if lines and not lines[-1].endswith('\n'): + lines[-1] += '\n' + lines.reverse() + if lines and iblock > 0: + spillover = lines.pop() + if lines: + line_count += len(lines) + data = ''.join(lines).replace('\0', '\n\t') + outstream.write(data) + return line_count + + +def FindSentinel(filename, blocksize=2**16): + """Return the sentinel line from the output file. + + Args: + filename: The filename of the output file. (We'll read this file.) + blocksize: Optional block size for buffering, for unit testing. + + Returns: + The contents of the last line in the file that doesn't start with + a tab, with its trailing newline stripped; or None if the file + couldn't be opened or no such line could be found by inspecting + the last 'blocksize' bytes of the file. + """ + if filename == '-': + StatusUpdate('Can\'t combine --append with output to stdout.') + sys.exit(2) + try: + fp = open(filename, 'rb') + except IOError, err: + StatusUpdate('Append mode disabled: can\'t read %r: %s.' % (filename, err)) + return None + try: + fp.seek(0, 2) + fp.seek(max(0, fp.tell() - blocksize)) + lines = fp.readlines() + del lines[:1] + sentinel = None + for line in lines: + if not line.startswith('\t'): + sentinel = line + if not sentinel: + StatusUpdate('Append mode disabled: can\'t find sentinel in %r.' % + filename) + return None + return sentinel.rstrip('\n') + finally: + fp.close() + + +class UploadBatcher(object): + """Helper to batch file uploads.""" + + def __init__(self, what, app_id, version, server): + """Constructor. + + Args: + what: Either 'file' or 'blob' or 'errorblob' indicating what kind of + objects this batcher uploads. Used in messages and URLs. + app_id: The application ID. + version: The application version string. + server: The RPC server. + """ + assert what in ('file', 'blob', 'errorblob'), repr(what) + self.what = what + self.app_id = app_id + self.version = version + self.server = server + self.single_url = '/api/appversion/add' + what + self.batch_url = self.single_url + 's' + self.batching = True + self.batch = [] + self.batch_size = 0 + + def SendBatch(self): + """Send the current batch on its way. + + If successful, resets self.batch and self.batch_size. + + Raises: + HTTPError with code=404 if the server doesn't support batching. + """ + boundary = 'boundary' + parts = [] + for path, payload, mime_type in self.batch: + while boundary in payload: + boundary += '%04x' % random.randint(0, 0xffff) + assert len(boundary) < 80, 'Unexpected error, please try again.' + part = '\n'.join(['', + 'X-Appcfg-File: %s' % urllib.quote(path), + 'X-Appcfg-Hash: %s' % _Hash(payload), + 'Content-Type: %s' % mime_type, + 'Content-Length: %d' % len(payload), + 'Content-Transfer-Encoding: 8bit', + '', + payload, + ]) + parts.append(part) + parts.insert(0, + 'MIME-Version: 1.0\n' + 'Content-Type: multipart/mixed; boundary="%s"\n' + '\n' + 'This is a message with multiple parts in MIME format.' % + boundary) + parts.append('--\n') + delimiter = '\n--%s' % boundary + payload = delimiter.join(parts) + logging.info('Uploading batch of %d %ss to %s with boundary="%s".', + len(self.batch), self.what, self.batch_url, boundary) + self.server.Send(self.batch_url, + payload=payload, + content_type='message/rfc822', + app_id=self.app_id, + version=self.version) + self.batch = [] + self.batch_size = 0 + + def SendSingleFile(self, path, payload, mime_type): + """Send a single file on its way.""" + logging.info('Uploading %s %s (%s bytes, type=%s) to %s.', + self.what, path, len(payload), mime_type, self.single_url) + self.server.Send(self.single_url, + payload=payload, + content_type=mime_type, + path=path, + app_id=self.app_id, + version=self.version) + + def Flush(self): + """Flush the current batch. + + This first attempts to send the batch as a single request; if that + fails because the server doesn't support batching, the files are + sent one by one, and self.batching is reset to False. + + At the end, self.batch and self.batch_size are reset. + """ + if not self.batch: + return + try: + self.SendBatch() + except urllib2.HTTPError, err: + if err.code != 404: + raise + + logging.info('Old server detected; turning off %s batching.', self.what) + self.batching = False + + for path, payload, mime_type in self.batch: + self.SendSingleFile(path, payload, mime_type) + + self.batch = [] + self.batch_size = 0 + + def AddToBatch(self, path, payload, mime_type): + """Batch a file, possibly flushing first, or perhaps upload it directly. + + Args: + path: The name of the file. + payload: The contents of the file. + mime_type: The MIME Content-type of the file, or None. + + If mime_type is None, application/octet-stream is substituted. + """ + if not mime_type: + mime_type = 'application/octet-stream' + size = len(payload) + if size <= MAX_BATCH_FILE_SIZE: + if (len(self.batch) >= MAX_BATCH_COUNT or + self.batch_size + size > MAX_BATCH_SIZE): + self.Flush() + if self.batching: + logging.info('Adding %s %s (%s bytes, type=%s) to batch.', + self.what, path, size, mime_type) + self.batch.append((path, payload, mime_type)) + self.batch_size += size + BATCH_OVERHEAD + return + self.SendSingleFile(path, payload, mime_type) + + +def _Hash(content): + """Compute the hash of the content. + + Args: + content: The data to hash as a string. + + Returns: + The string representation of the hash. + """ + h = sha.new(content).hexdigest() + return '%s_%s_%s_%s_%s' % (h[0:8], h[8:16], h[16:24], h[24:32], h[32:40]) + + +class AppVersionUpload(object): + """Provides facilities to upload a new appversion to the hosting service. + + Attributes: + server: The AbstractRpcServer to use for the upload. + config: The AppInfoExternal object derived from the app.yaml file. + app_id: The application string from 'config'. + version: The version string from 'config'. + files: A dictionary of files to upload to the server, mapping path to + hash of the file contents. + in_transaction: True iff a transaction with the server has started. + An AppVersionUpload can do only one transaction at a time. + deployed: True iff the Deploy method has been called. + """ + + def __init__(self, server, config): + """Creates a new AppVersionUpload. + + Args: + server: The RPC server to use. Should be an instance of HttpRpcServer or + TestRpcServer. + config: An AppInfoExternal object that specifies the configuration for + this application. + """ + self.server = server + self.config = config + self.app_id = self.config.application + self.version = self.config.version + + self.files = {} + + self.in_transaction = False + self.deployed = False + self.batching = True + self.file_batcher = UploadBatcher('file', self.app_id, self.version, + self.server) + self.blob_batcher = UploadBatcher('blob', self.app_id, self.version, + self.server) + self.errorblob_batcher = UploadBatcher('errorblob', self.app_id, + self.version, self.server) + + def AddFile(self, path, file_handle): + """Adds the provided file to the list to be pushed to the server. + + Args: + path: The path the file should be uploaded as. + file_handle: A stream containing data to upload. + """ + assert not self.in_transaction, 'Already in a transaction.' + assert file_handle is not None + + reason = appinfo.ValidFilename(path) + if reason: + logging.error(reason) + return + + pos = file_handle.tell() + content_hash = _Hash(file_handle.read()) + file_handle.seek(pos, 0) + + self.files[path] = content_hash + + def Begin(self): + """Begins the transaction, returning a list of files that need uploading. + + All calls to AddFile must be made before calling Begin(). + + Returns: + A list of pathnames for files that should be uploaded using UploadFile() + before Commit() can be called. + """ + assert not self.in_transaction, 'Already in a transaction.' + + StatusUpdate('Initiating update.') + self.server.Send('/api/appversion/create', app_id=self.app_id, + version=self.version, payload=self.config.ToYAML()) + self.in_transaction = True + + files_to_clone = [] + blobs_to_clone = [] + errorblobs = {} + for path, content_hash in self.files.iteritems(): + match_found = False + + mime_type = GetMimeTypeIfStaticFile(self.config, path) + if mime_type is not None: + blobs_to_clone.append((path, content_hash, mime_type)) + match_found = True + + (mime_type, error_code) = LookupErrorBlob(self.config, path) + if mime_type is not None: + errorblobs[path] = content_hash + match_found = True + + if not match_found: + files_to_clone.append((path, content_hash)) + + files_to_upload = {} + + def CloneFiles(url, files, file_type): + """Sends files to the given url. + + Args: + url: the server URL to use. + files: a list of files + file_type: the type of the files + """ + if not files: + return + + StatusUpdate('Cloning %d %s file%s.' % + (len(files), file_type, len(files) != 1 and 's' or '')) + for i in xrange(0, len(files), MAX_FILES_TO_CLONE): + if i > 0 and i % MAX_FILES_TO_CLONE == 0: + StatusUpdate('Cloned %d files.' % i) + + chunk = files[i:min(len(files), i + MAX_FILES_TO_CLONE)] + result = self.server.Send(url, + app_id=self.app_id, version=self.version, + payload=BuildClonePostBody(chunk)) + if result: + files_to_upload.update(dict( + (f, self.files[f]) for f in result.split(LIST_DELIMITER))) + + CloneFiles('/api/appversion/cloneblobs', blobs_to_clone, 'static') + CloneFiles('/api/appversion/clonefiles', files_to_clone, 'application') + + logging.debug('Files to upload: %s', files_to_upload) + + for (path, content_hash) in errorblobs.iteritems(): + files_to_upload[path] = content_hash + self.files = files_to_upload + return sorted(files_to_upload.iterkeys()) + + def UploadFile(self, path, file_handle): + """Uploads a file to the hosting service. + + Must only be called after Begin(). + The path provided must be one of those that were returned by Begin(). + + Args: + path: The path the file is being uploaded as. + file_handle: A file-like object containing the data to upload. + + Raises: + KeyError: The provided file is not amongst those to be uploaded. + """ + assert self.in_transaction, 'Begin() must be called before UploadFile().' + if path not in self.files: + raise KeyError('File \'%s\' is not in the list of files to be uploaded.' + % path) + + del self.files[path] + + match_found = False + mime_type = GetMimeTypeIfStaticFile(self.config, path) + payload = file_handle.read() + if mime_type is not None: + self.blob_batcher.AddToBatch(path, payload, mime_type) + match_found = True + + (mime_type, error_code) = LookupErrorBlob(self.config, path) + if mime_type is not None: + self.errorblob_batcher.AddToBatch(error_code, payload, mime_type) + match_found = True + + if not match_found: + self.file_batcher.AddToBatch(path, payload, None) + + + def Precompile(self): + """Handle bytecode precompilation.""" + StatusUpdate('Precompilation starting.') + files = [] + while True: + if files: + StatusUpdate('Precompilation: %d files left.' % len(files)) + files = self.PrecompileBatch(files) + if not files: + break + StatusUpdate('Precompilation completed.') + + def PrecompileBatch(self, files): + """Precompile a batch of files. + + Args: + files: Either an empty list (for the initial request) or a list + of files to be precompiled. + + Returns: + Either an empty list (if no more files need to be precompiled) + or a list of files to be precompiled subsequently. + """ + payload = LIST_DELIMITER.join(files) + response = self.server.Send('/api/appversion/precompile', + app_id=self.app_id, + version=self.version, + payload=payload) + if not response: + return [] + return response.split(LIST_DELIMITER) + + def Commit(self): + """Commits the transaction, making the new app version available. + + All the files returned by Begin() must have been uploaded with UploadFile() + before Commit() can be called. + + This tries the new 'deploy' method; if that fails it uses the old 'commit'. + + Raises: + Exception: Some required files were not uploaded. + """ + assert self.in_transaction, 'Begin() must be called before Commit().' + if self.files: + raise Exception('Not all required files have been uploaded.') + + try: + self.Deploy() + if not RetryWithBackoff(1, 2, 60, 20, self.IsReady): + logging.warning('Version still not ready to serve, aborting.') + raise Exception('Version not ready.') + self.StartServing() + except urllib2.HTTPError, e: + if e.code != 404: + raise + StatusUpdate('Closing update.') + self.server.Send('/api/appversion/commit', app_id=self.app_id, + version=self.version) + self.in_transaction = False + + def Deploy(self): + """Deploys the new app version but does not make it default. + + All the files returned by Begin() must have been uploaded with UploadFile() + before Deploy() can be called. + + Raises: + Exception: Some required files were not uploaded. + """ + assert self.in_transaction, 'Begin() must be called before Deploy().' + if self.files: + raise Exception('Not all required files have been uploaded.') + + StatusUpdate('Deploying new version.') + self.server.Send('/api/appversion/deploy', app_id=self.app_id, + version=self.version) + self.deployed = True + + def IsReady(self): + """Check if the new app version is ready to serve traffic. + + Raises: + Exception: Deploy has not yet been called. + + Returns: + True if the server returned the app is ready to serve. + """ + assert self.deployed, 'Deploy() must be called before IsReady().' + + StatusUpdate('Checking if new version is ready to serve.') + result = self.server.Send('/api/appversion/isready', app_id=self.app_id, + version=self.version) + return result == '1' + + def StartServing(self): + """Start serving with the newly created version. + + Raises: + Exception: Deploy has not yet been called. + """ + assert self.deployed, 'Deploy() must be called before IsReady().' + + StatusUpdate('Closing update: new version is ready to start serving.') + self.server.Send('/api/appversion/startserving', + app_id=self.app_id, version=self.version) + self.in_transaction = False + + def Rollback(self): + """Rolls back the transaction if one is in progress.""" + if not self.in_transaction: + return + StatusUpdate('Rolling back the update.') + self.server.Send('/api/appversion/rollback', app_id=self.app_id, + version=self.version) + self.in_transaction = False + self.files = {} + + def DoUpload(self, paths, max_size, openfunc): + """Uploads a new appversion with the given config and files to the server. + + Args: + paths: An iterator that yields the relative paths of the files to upload. + max_size: The maximum size file to upload. + openfunc: A function that takes a path and returns a file-like object. + """ + logging.info('Reading app configuration.') + + path = '' + try: + StatusUpdate('Scanning files on local disk.') + num_files = 0 + for path in paths: + file_handle = openfunc(path) + try: + file_length = GetFileLength(file_handle) + if file_length > max_size: + logging.error('Ignoring file \'%s\': Too long ' + '(max %d bytes, file is %d bytes)', + path, max_size, file_length) + else: + logging.info('Processing file \'%s\'', path) + self.AddFile(path, file_handle) + finally: + file_handle.close() + num_files += 1 + if num_files % 500 == 0: + StatusUpdate('Scanned %d files.' % num_files) + except KeyboardInterrupt: + logging.info('User interrupted. Aborting.') + raise + except EnvironmentError, e: + logging.error('An error occurred processing file \'%s\': %s. Aborting.', + path, e) + raise + + try: + missing_files = self.Begin() + if missing_files: + StatusUpdate('Uploading %d files and blobs.' % len(missing_files)) + num_files = 0 + for missing_file in missing_files: + file_handle = openfunc(missing_file) + try: + self.UploadFile(missing_file, file_handle) + finally: + file_handle.close() + num_files += 1 + if num_files % 500 == 0: + StatusUpdate('Processed %d out of %s.' % + (num_files, len(missing_files))) + self.file_batcher.Flush() + self.blob_batcher.Flush() + self.errorblob_batcher.Flush() + StatusUpdate('Uploaded %d files and blobs' % num_files) + + if (self.config.derived_file_type and + appinfo.PYTHON_PRECOMPILED in self.config.derived_file_type): + self.Precompile() + + self.Commit() + + except KeyboardInterrupt: + logging.info('User interrupted. Aborting.') + self.Rollback() + raise + except urllib2.HTTPError, err: + logging.info('HTTP Error (%s)', err) + self.Rollback() + raise + except: + logging.exception('An unexpected error occurred. Aborting.') + self.Rollback() + raise + + logging.info('Done!') + + +def FileIterator(base, skip_files, separator=os.path.sep): + """Walks a directory tree, returning all the files. Follows symlinks. + + Args: + base: The base path to search for files under. + skip_files: A regular expression object for files/directories to skip. + separator: Path separator used by the running system's platform. + + Yields: + Paths of files found, relative to base. + """ + dirs = [''] + while dirs: + current_dir = dirs.pop() + for entry in os.listdir(os.path.join(base, current_dir)): + name = os.path.join(current_dir, entry) + fullname = os.path.join(base, name) + if separator == '\\': + name = name.replace('\\', '/') + if os.path.isfile(fullname): + if skip_files.match(name): + logging.info('Ignoring file \'%s\': File matches ignore regex.', name) + else: + yield name + elif os.path.isdir(fullname): + if skip_files.match(name): + logging.info( + 'Ignoring directory \'%s\': Directory matches ignore regex.', + name) + else: + dirs.append(name) + + +def GetFileLength(fh): + """Returns the length of the file represented by fh. + + This function is capable of finding the length of any seekable stream, + unlike os.fstat, which only works on file streams. + + Args: + fh: The stream to get the length of. + + Returns: + The length of the stream. + """ + pos = fh.tell() + fh.seek(0, 2) + length = fh.tell() + fh.seek(pos, 0) + return length + + +def GetUserAgent(get_version=GetVersionObject, + get_platform=appengine_rpc.GetPlatformToken): + """Determines the value of the 'User-agent' header to use for HTTP requests. + + If the 'APPCFG_SDK_NAME' environment variable is present, that will be + used as the first product token in the user-agent. + + Args: + get_version: Used for testing. + get_platform: Used for testing. + + Returns: + String containing the 'user-agent' header value, which includes the SDK + version, the platform information, and the version of Python; + e.g., 'appcfg_py/1.0.1 Darwin/9.2.0 Python/2.5.2'. + """ + product_tokens = [] + + sdk_name = os.environ.get('APPCFG_SDK_NAME') + if sdk_name: + product_tokens.append(sdk_name) + else: + version = get_version() + if version is None: + release = 'unknown' + else: + release = version['release'] + + product_tokens.append('appcfg_py/%s' % release) + + product_tokens.append(get_platform()) + + python_version = '.'.join(str(i) for i in sys.version_info) + product_tokens.append('Python/%s' % python_version) + + return ' '.join(product_tokens) + + +def GetSourceName(get_version=GetVersionObject): + """Gets the name of this source version.""" + version = get_version() + if version is None: + release = 'unknown' + else: + release = version['release'] + return 'Google-appcfg-%s' % (release,) + + +class AppCfgApp(object): + """Singleton class to wrap AppCfg tool functionality. + + This class is responsible for parsing the command line and executing + the desired action on behalf of the user. Processing files and + communicating with the server is handled by other classes. + + Attributes: + actions: A dictionary mapping action names to Action objects. + action: The Action specified on the command line. + parser: An instance of optparse.OptionParser. + options: The command line options parsed by 'parser'. + argv: The original command line as a list. + args: The positional command line args left over after parsing the options. + raw_input_fn: Function used for getting raw user input, like email. + password_input_fn: Function used for getting user password. + error_fh: Unexpected HTTPErrors are printed to this file handle. + + Attributes for testing: + parser_class: The class to use for parsing the command line. Because + OptionsParser will exit the program when there is a parse failure, it + is nice to subclass OptionsParser and catch the error before exiting. + """ + + def __init__(self, argv, parser_class=optparse.OptionParser, + rpc_server_class=appengine_rpc.HttpRpcServer, + raw_input_fn=raw_input, + password_input_fn=getpass.getpass, + error_fh=sys.stderr, + update_check_class=UpdateCheck): + """Initializer. Parses the cmdline and selects the Action to use. + + Initializes all of the attributes described in the class docstring. + Prints help or error messages if there is an error parsing the cmdline. + + Args: + argv: The list of arguments passed to this program. + parser_class: Options parser to use for this application. + rpc_server_class: RPC server class to use for this application. + raw_input_fn: Function used for getting user email. + password_input_fn: Function used for getting user password. + error_fh: Unexpected HTTPErrors are printed to this file handle. + update_check_class: UpdateCheck class (can be replaced for testing). + """ + self.parser_class = parser_class + self.argv = argv + self.rpc_server_class = rpc_server_class + self.raw_input_fn = raw_input_fn + self.password_input_fn = password_input_fn + self.error_fh = error_fh + self.update_check_class = update_check_class + + self.parser = self._GetOptionParser() + for action in self.actions.itervalues(): + action.options(self, self.parser) + + self.options, self.args = self.parser.parse_args(argv[1:]) + + if len(self.args) < 1: + self._PrintHelpAndExit() + if self.args[0] not in self.actions: + self.parser.error('Unknown action \'%s\'\n%s' % + (self.args[0], self.parser.get_description())) + action_name = self.args.pop(0) + self.action = self.actions[action_name] + + self.parser, self.options = self._MakeSpecificParser(self.action) + + if self.options.help: + self._PrintHelpAndExit() + + if self.options.verbose == 2: + logging.getLogger().setLevel(logging.INFO) + elif self.options.verbose == 3: + logging.getLogger().setLevel(logging.DEBUG) + + global verbosity + verbosity = self.options.verbose + + def Run(self): + """Executes the requested action. + + Catches any HTTPErrors raised by the action and prints them to stderr. + + Returns: + 1 on error, 0 if successful. + """ + try: + self.action(self) + except urllib2.HTTPError, e: + body = e.read() + print >>self.error_fh, ('Error %d: --- begin server output ---\n' + '%s\n--- end server output ---' % + (e.code, body.rstrip('\n'))) + return 1 + except yaml_errors.EventListenerError, e: + print >>self.error_fh, ('Error parsing yaml file:\n%s' % e) + return 1 + return 0 + + def _GetActionDescriptions(self): + """Returns a formatted string containing the short_descs for all actions.""" + action_names = self.actions.keys() + action_names.sort() + desc = '' + for action_name in action_names: + desc += ' %s: %s\n' % (action_name, self.actions[action_name].short_desc) + return desc + + def _GetOptionParser(self): + """Creates an OptionParser with generic usage and description strings. + + Returns: + An OptionParser instance. + """ + + class Formatter(optparse.IndentedHelpFormatter): + """Custom help formatter that does not reformat the description.""" + + def format_description(self, description): + """Very simple formatter.""" + return description + '\n' + + desc = self._GetActionDescriptions() + desc = ('Action must be one of:\n%s' + 'Use \'help \' for a detailed description.') % desc + + parser = self.parser_class(usage='%prog [options] ', + description=desc, + formatter=Formatter(), + conflict_handler='resolve') + parser.add_option('-h', '--help', action='store_true', + dest='help', help='Show the help message and exit.') + parser.add_option('-q', '--quiet', action='store_const', const=0, + dest='verbose', help='Print errors only.') + parser.add_option('-v', '--verbose', action='store_const', const=2, + dest='verbose', default=1, + help='Print info level logs.') + parser.add_option('--noisy', action='store_const', const=3, + dest='verbose', help='Print all logs.') + parser.add_option('-s', '--server', action='store', dest='server', + default='appengine.google.com', + metavar='SERVER', help='The server to connect to.') + parser.add_option('--secure', action='store_true', dest='secure', + default=True, help=optparse.SUPPRESS_HELP) + parser.add_option('--insecure', action='store_false', dest='secure', + help='Use HTTP when communicating with the server.') + parser.add_option('-e', '--email', action='store', dest='email', + metavar='EMAIL', default=None, + help='The username to use. Will prompt if omitted.') + parser.add_option('-H', '--host', action='store', dest='host', + metavar='HOST', default=None, + help='Overrides the Host header sent with all RPCs.') + parser.add_option('--no_cookies', action='store_false', + dest='save_cookies', default=True, + help='Do not save authentication cookies to local disk.') + parser.add_option('--passin', action='store_true', + dest='passin', default=False, + help='Read the login password from stdin.') + parser.add_option('-A', '--application', action='store', dest='app_id', + help='Override application from app.yaml file.') + parser.add_option('-V', '--version', action='store', dest='version', + help='Override (major) version from app.yaml file.') + return parser + + def _MakeSpecificParser(self, action): + """Creates a new parser with documentation specific to 'action'. + + Args: + action: An Action instance to be used when initializing the new parser. + + Returns: + A tuple containing: + parser: An instance of OptionsParser customized to 'action'. + options: The command line options after re-parsing. + """ + parser = self._GetOptionParser() + parser.set_usage(action.usage) + parser.set_description('%s\n%s' % (action.short_desc, action.long_desc)) + action.options(self, parser) + options, unused_args = parser.parse_args(self.argv[1:]) + return parser, options + + def _PrintHelpAndExit(self, exit_code=2): + """Prints the parser's help message and exits the program. + + Args: + exit_code: The integer code to pass to sys.exit(). + """ + self.parser.print_help() + sys.exit(exit_code) + + def _GetRpcServer(self): + """Returns an instance of an AbstractRpcServer. + + Returns: + A new AbstractRpcServer, on which RPC calls can be made. + """ + + def GetUserCredentials(): + """Prompts the user for a username and password.""" + email = self.options.email + if email is None: + email = self.raw_input_fn('Email: ') + + password_prompt = 'Password for %s: ' % email + if self.options.passin: + password = self.raw_input_fn(password_prompt) + else: + password = self.password_input_fn(password_prompt) + + return (email, password) + + StatusUpdate('Server: %s.' % self.options.server) + + if self.options.host and self.options.host == 'localhost': + email = self.options.email + if email is None: + email = 'test@example.com' + logging.info('Using debug user %s. Override with --email', email) + server = self.rpc_server_class( + self.options.server, + lambda: (email, 'password'), + GetUserAgent(), + GetSourceName(), + host_override=self.options.host, + save_cookies=self.options.save_cookies, + + secure=False) + server.authenticated = True + return server + + if self.options.passin: + auth_tries = 1 + else: + auth_tries = 3 + + return self.rpc_server_class(self.options.server, GetUserCredentials, + GetUserAgent(), GetSourceName(), + host_override=self.options.host, + save_cookies=self.options.save_cookies, + auth_tries=auth_tries, + account_type='HOSTED_OR_GOOGLE', + secure=self.options.secure) + + def _FindYaml(self, basepath, file_name): + """Find yaml files in application directory. + + Args: + basepath: Base application directory. + file_name: Filename without extension to search for. + + Returns: + Path to located yaml file if one exists, else None. + """ + if not os.path.isdir(basepath): + self.parser.error('Not a directory: %s' % basepath) + + for yaml_file in (file_name + '.yaml', file_name + '.yml'): + yaml_path = os.path.join(basepath, yaml_file) + if os.path.isfile(yaml_path): + return yaml_path + + return None + + def _ParseAppYaml(self, basepath): + """Parses the app.yaml file. + + Args: + basepath: the directory of the application. + + Returns: + An AppInfoExternal object. + """ + appyaml_filename = self._FindYaml(basepath, 'app') + if appyaml_filename is None: + self.parser.error('Directory does not contain an app.yaml ' + 'configuration file.') + + fh = open(appyaml_filename, 'r') + try: + appyaml = appinfo.LoadSingleAppInfo(fh) + finally: + fh.close() + orig_application = appyaml.application + orig_version = appyaml.version + if self.options.app_id: + appyaml.application = self.options.app_id + if self.options.version: + appyaml.version = self.options.version + msg = 'Application: %s' % appyaml.application + if appyaml.application != orig_application: + msg += ' (was: %s)' % orig_application + msg += '; version: %s' % appyaml.version + if appyaml.version != orig_version: + msg += ' (was: %s)' % orig_version + msg += '.' + StatusUpdate(msg) + return appyaml + + def _ParseYamlFile(self, basepath, basename, parser): + """Parses the a yaml file. + + Args: + basepath: the directory of the application. + basename: the base name of the file (with the '.yaml' stripped off). + parser: the function or method used to parse the file. + + Returns: + A single parsed yaml file or None if the file does not exist. + """ + file_name = self._FindYaml(basepath, basename) + if file_name is not None: + fh = open(file_name, 'r') + try: + defns = parser(fh) + finally: + fh.close() + return defns + return None + + def _ParseIndexYaml(self, basepath): + """Parses the index.yaml file. + + Args: + basepath: the directory of the application. + + Returns: + A single parsed yaml file or None if the file does not exist. + """ + return self._ParseYamlFile(basepath, 'index', + datastore_index.ParseIndexDefinitions) + + def _ParseCronYaml(self, basepath): + """Parses the cron.yaml file. + + Args: + basepath: the directory of the application. + + Returns: + A CronInfoExternal object or None if the file does not exist. + """ + return self._ParseYamlFile(basepath, 'cron', croninfo.LoadSingleCron) + + def _ParseQueueYaml(self, basepath): + """Parses the queue.yaml file. + + Args: + basepath: the directory of the application. + + Returns: + A CronInfoExternal object or None if the file does not exist. + """ + return self._ParseYamlFile(basepath, 'queue', queueinfo.LoadSingleQueue) + + def _ParseDosYaml(self, basepath): + """Parses the dos.yaml file. + + Args: + basepath: the directory of the application. + + Returns: + A DosInfoExternal object or None if the file does not exist. + """ + return self._ParseYamlFile(basepath, 'dos', dosinfo.LoadSingleDos) + + def Help(self): + """Prints help for a specific action. + + Expects self.args[0] to contain the name of the action in question. + Exits the program after printing the help message. + """ + if len(self.args) != 1 or self.args[0] not in self.actions: + self.parser.error('Expected a single action argument. Must be one of:\n' + + self._GetActionDescriptions()) + + action = self.actions[self.args[0]] + self.parser, unused_options = self._MakeSpecificParser(action) + self._PrintHelpAndExit(exit_code=0) + + def Update(self): + """Updates and deploys a new appversion.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + + updatecheck = self.update_check_class(rpc_server, appyaml) + updatecheck.CheckForUpdates() + + appversion = AppVersionUpload(rpc_server, appyaml) + appversion.DoUpload(FileIterator(basepath, appyaml.skip_files), + self.options.max_size, + lambda path: open(os.path.join(basepath, path), 'rb')) + + index_defs = self._ParseIndexYaml(basepath) + if index_defs: + index_upload = IndexDefinitionUpload(rpc_server, appyaml, index_defs) + try: + index_upload.DoUpload() + except urllib2.HTTPError, e: + StatusUpdate('Error %d: --- begin server output ---\n' + '%s\n--- end server output ---' % + (e.code, e.read().rstrip('\n'))) + print >> self.error_fh, ( + 'Your app was updated, but there was an error updating your ' + 'indexes. Please retry later with appcfg.py update_indexes.') + + cron_entries = self._ParseCronYaml(basepath) + if cron_entries: + cron_upload = CronEntryUpload(rpc_server, appyaml, cron_entries) + cron_upload.DoUpload() + + queue_entries = self._ParseQueueYaml(basepath) + if queue_entries: + queue_upload = QueueEntryUpload(rpc_server, appyaml, queue_entries) + queue_upload.DoUpload() + + dos_entries = self._ParseDosYaml(basepath) + if dos_entries: + dos_upload = DosEntryUpload(rpc_server, appyaml, dos_entries) + dos_upload.DoUpload() + + def _UpdateOptions(self, parser): + """Adds update-specific options to 'parser'. + + Args: + parser: An instance of OptionsParser. + """ + parser.add_option('-S', '--max_size', type='int', dest='max_size', + default=10485760, metavar='SIZE', + help='Maximum size of a file to upload.') + + def VacuumIndexes(self): + """Deletes unused indexes.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + config = self._ParseAppYaml(basepath) + + index_defs = self._ParseIndexYaml(basepath) + if index_defs is None: + index_defs = datastore_index.IndexDefinitions() + + rpc_server = self._GetRpcServer() + vacuum = VacuumIndexesOperation(rpc_server, + config, + self.options.force_delete) + vacuum.DoVacuum(index_defs) + + def _VacuumIndexesOptions(self, parser): + """Adds vacuum_indexes-specific options to 'parser'. + + Args: + parser: An instance of OptionsParser. + """ + parser.add_option('-f', '--force', action='store_true', dest='force_delete', + default=False, + help='Force deletion without being prompted.') + + def UpdateCron(self): + """Updates any new or changed cron definitions.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + + cron_entries = self._ParseCronYaml(basepath) + if cron_entries: + cron_upload = CronEntryUpload(rpc_server, appyaml, cron_entries) + cron_upload.DoUpload() + + def UpdateIndexes(self): + """Updates indexes.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + + index_defs = self._ParseIndexYaml(basepath) + if index_defs: + index_upload = IndexDefinitionUpload(rpc_server, appyaml, index_defs) + index_upload.DoUpload() + + def UpdateQueues(self): + """Updates any new or changed task queue definitions.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + + queue_entries = self._ParseQueueYaml(basepath) + if queue_entries: + queue_upload = QueueEntryUpload(rpc_server, appyaml, queue_entries) + queue_upload.DoUpload() + + def UpdateDos(self): + """Updates any new or changed dos definitions.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + + dos_entries = self._ParseDosYaml(basepath) + if dos_entries: + dos_upload = DosEntryUpload(rpc_server, appyaml, dos_entries) + dos_upload.DoUpload() + + def Rollback(self): + """Does a rollback of any existing transaction for this app version.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + + appversion = AppVersionUpload(self._GetRpcServer(), appyaml) + appversion.in_transaction = True + appversion.Rollback() + + def SetDefaultVersion(self): + """Sets the default version.""" + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + + version_setter = DefaultVersionSet(self._GetRpcServer(), appyaml) + version_setter.SetVersion() + + def RequestLogs(self): + """Write request logs to a file.""" + if len(self.args) != 2: + self.parser.error( + 'Expected a argument and an argument.') + if (self.options.severity is not None and + not 0 <= self.options.severity <= MAX_LOG_LEVEL): + self.parser.error( + 'Severity range is 0 (DEBUG) through %s (CRITICAL).' % MAX_LOG_LEVEL) + + if self.options.num_days is None: + self.options.num_days = int(not self.options.append) + + try: + end_date = self._ParseEndDate(self.options.end_date) + except (TypeError, ValueError): + self.parser.error('End date must be in the format YYYY-MM-DD.') + + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + rpc_server = self._GetRpcServer() + logs_requester = LogsRequester(rpc_server, appyaml, self.args[1], + self.options.num_days, + self.options.append, + self.options.severity, + end_date, + self.options.vhost, + self.options.include_vhost, + self.options.include_all) + logs_requester.DownloadLogs() + + def _ParseEndDate(self, date, time_func=time.time): + """Translates an ISO 8601 date to a date object. + + Args: + date: A date string as YYYY-MM-DD. + time_func: time.time() function for testing. + + Returns: + A date object representing the last day of logs to get. + If no date is given, returns today in the US/Pacific timezone. + """ + if not date: + return PacificDate(time_func()) + return datetime.date(*[int(i) for i in date.split('-')]) + + def _RequestLogsOptions(self, parser): + """Adds request_logs-specific options to 'parser'. + + Args: + parser: An instance of OptionsParser. + """ + parser.add_option('-n', '--num_days', type='int', dest='num_days', + action='store', default=None, + help='Number of days worth of log data to get. ' + 'The cut-off point is midnight US/Pacific. ' + 'Use 0 to get all available logs. ' + 'Default is 1, unless --append is also given; ' + 'then the default is 0.') + parser.add_option('-a', '--append', dest='append', + action='store_true', default=False, + help='Append to existing file.') + parser.add_option('--severity', type='int', dest='severity', + action='store', default=None, + help='Severity of app-level log messages to get. ' + 'The range is 0 (DEBUG) through 4 (CRITICAL). ' + 'If omitted, only request logs are returned.') + parser.add_option('--vhost', type='string', dest='vhost', + action='store', default=None, + help='The virtual host of log messages to get. ' + 'If omitted, all log messages are returned.') + parser.add_option('--include_vhost', dest='include_vhost', + action='store_true', default=False, + help='Include virtual host in log messages.') + parser.add_option('--include_all', dest='include_all', + action='store_true', default=None, + help='Include everything in log messages.') + parser.add_option('--end_date', dest='end_date', + action='store', default='', + help='End date (as YYYY-MM-DD) of period for log data. ' + 'Defaults to today.') + + def CronInfo(self, now=None, output=sys.stdout): + """Displays information about cron definitions. + + Args: + now: used for testing. + output: Used for testing. + """ + if len(self.args) != 1: + self.parser.error('Expected a single argument.') + if now is None: + now = datetime.datetime.now() + + basepath = self.args[0] + cron_entries = self._ParseCronYaml(basepath) + if cron_entries and cron_entries.cron: + for entry in cron_entries.cron: + description = entry.description + if not description: + description = '' + print >>output, '\n%s:\nURL: %s\nSchedule: %s' % (description, + entry.url, + entry.schedule) + schedule = groctimespecification.GrocTimeSpecification(entry.schedule) + matches = schedule.GetMatches(now, self.options.num_runs) + for match in matches: + print >>output, '%s, %s from now' % ( + match.strftime('%Y-%m-%d %H:%M:%S'), match - now) + + def _CronInfoOptions(self, parser): + """Adds cron_info-specific options to 'parser'. + + Args: + parser: An instance of OptionsParser. + """ + parser.add_option('-n', '--num_runs', type='int', dest='num_runs', + action='store', default=5, + help='Number of runs of each cron job to display' + 'Default is 5') + + def _CheckRequiredLoadOptions(self): + """Checks that upload/download options are present.""" + for option in ['filename',]: + if getattr(self.options, option) is None: + self.parser.error('Option \'%s\' is required.' % option) + if not self.options.url: + self.parser.error('You must have google.appengine.ext.remote_api.handler ' + 'assigned to an endpoint in app.yaml, or provide ' + 'the url of the handler via the \'url\' option.') + + def InferRemoteApiUrl(self, appyaml): + """Uses app.yaml to determine the remote_api endpoint. + + Args: + appyaml: A parsed app.yaml file. + + Returns: + The url of the remote_api endpoint as a string, or None + """ + handlers = appyaml.handlers + handler_suffix = 'remote_api/handler.py' + app_id = appyaml.application + for handler in handlers: + if hasattr(handler, 'script') and handler.script: + if handler.script.endswith(handler_suffix): + server = self.options.server + if server == 'appengine.google.com': + return 'http://%s.appspot.com%s' % (app_id, handler.url) + else: + return 'http://%s%s' % (server, handler.url) + return None + + def RunBulkloader(self, arg_dict): + """Invokes the bulkloader with the given keyword arguments. + + Args: + arg_dict: Dictionary of arguments to pass to bulkloader.Run(). + """ + try: + import sqlite3 + except ImportError: + logging.error('upload_data action requires SQLite3 and the python ' + 'sqlite3 module (included in python since 2.5).') + sys.exit(1) + + sys.exit(bulkloader.Run(arg_dict)) + + def _SetupLoad(self): + """Performs common verification and set up for upload and download.""" + if len(self.args) != 1 and not self.options.url: + self.parser.error('Expected either --url or a single ' + 'argument.') + + if len(self.args) == 1: + basepath = self.args[0] + appyaml = self._ParseAppYaml(basepath) + + self.options.app_id = appyaml.application + + if not self.options.url: + url = self.InferRemoteApiUrl(appyaml) + if url is not None: + self.options.url = url + + self._CheckRequiredLoadOptions() + + if self.options.batch_size < 1: + self.parser.error('batch_size must be 1 or larger.') + + if verbosity == 1: + logging.getLogger().setLevel(logging.INFO) + self.options.debug = False + else: + logging.getLogger().setLevel(logging.DEBUG) + self.options.debug = True + + def _MakeLoaderArgs(self): + args = dict([(arg_name, getattr(self.options, arg_name, None)) for + arg_name in ( + 'url', + 'filename', + 'batch_size', + 'kind', + 'num_threads', + 'bandwidth_limit', + 'rps_limit', + 'http_limit', + 'db_filename', + 'config_file', + 'auth_domain', + 'has_header', + 'loader_opts', + 'log_file', + 'passin', + 'email', + 'debug', + 'exporter_opts', + 'mapper_opts', + 'result_db_filename', + 'mapper_opts', + 'dry_run', + 'dump', + 'restore', + 'namespace', + 'create_config', + )]) + args['application'] = self.options.app_id + return args + + def PerformDownload(self, run_fn=None): + """Performs a datastore download via the bulkloader. + + Args: + run_fn: Function to invoke the bulkloader, used for testing. + """ + if run_fn is None: + run_fn = self.RunBulkloader + self._SetupLoad() + + StatusUpdate('Downloading data records.') + + args = self._MakeLoaderArgs() + args['download'] = bool(args['config_file']) + args['has_header'] = False + args['map'] = False + args['dump'] = not args['config_file'] + args['restore'] = False + args['create_config'] = False + + run_fn(args) + + def PerformUpload(self, run_fn=None): + """Performs a datastore upload via the bulkloader. + + Args: + run_fn: Function to invoke the bulkloader, used for testing. + """ + if run_fn is None: + run_fn = self.RunBulkloader + self._SetupLoad() + + StatusUpdate('Uploading data records.') + + args = self._MakeLoaderArgs() + args['download'] = False + args['map'] = False + args['dump'] = False + args['restore'] = not args['config_file'] + args['create_config'] = False + + run_fn(args) + + def CreateBulkloadConfig(self, run_fn=None): + """Create a bulkloader config via the bulkloader wizard. + + Args: + run_fn: Function to invoke the bulkloader, used for testing. + """ + if run_fn is None: + run_fn = self.RunBulkloader + self._SetupLoad() + + StatusUpdate('Creating bulkloader configuration.') + + args = self._MakeLoaderArgs() + args['download'] = False + args['has_header'] = False + args['map'] = False + args['dump'] = False + args['restore'] = False + args['create_config'] = True + + run_fn(args) + + def _PerformLoadOptions(self, parser): + """Adds options common to 'upload_data' and 'download_data'. + + Args: + parser: An instance of OptionsParser. + """ + parser.add_option('--filename', type='string', dest='filename', + action='store', + help='The name of the file containing the input data.' + ' (Required)') + parser.add_option('--kind', type='string', dest='kind', + action='store', + help='The kind of the entities to store.') + parser.add_option('--url', type='string', dest='url', + action='store', + help='The location of the remote_api endpoint.') + parser.add_option('--num_threads', type='int', dest='num_threads', + action='store', default=10, + help='Number of threads to upload records with.') + parser.add_option('--batch_size', type='int', dest='batch_size', + action='store', default=10, + help='Number of records to post in each request.') + parser.add_option('--bandwidth_limit', type='int', dest='bandwidth_limit', + action='store', default=250000, + help='The maximum bytes/second bandwidth for transfers.') + parser.add_option('--rps_limit', type='int', dest='rps_limit', + action='store', default=20, + help='The maximum records/second for transfers.') + parser.add_option('--http_limit', type='int', dest='http_limit', + action='store', default=8, + help='The maximum requests/second for transfers.') + parser.add_option('--db_filename', type='string', dest='db_filename', + action='store', + help='Name of the progress database file.') + parser.add_option('--auth_domain', type='string', dest='auth_domain', + action='store', default='gmail.com', + help='The name of the authorization domain to use.') + parser.add_option('--log_file', type='string', dest='log_file', + help='File to write bulkloader logs. If not supplied ' + 'then a new log file will be created, named: ' + 'bulkloader-log-TIMESTAMP.') + parser.add_option('--dry_run', action='store_true', + dest='dry_run', default=False, + help='Do not execute any remote_api calls') + parser.add_option('--namespace', type='string', dest='namespace', + action='store', default='', + help='Namespace to use when accessing datastore.') + + def _PerformUploadOptions(self, parser): + """Adds 'upload_data' specific options to the 'parser' passed in. + + Args: + parser: An instance of OptionsParser. + """ + self._PerformLoadOptions(parser) + parser.add_option('--has_header', dest='has_header', + action='store_true', default=False, + help='Whether the first line of the input file should be' + ' skipped') + parser.add_option('--loader_opts', type='string', dest='loader_opts', + help='A string to pass to the Loader.initialize method.') + parser.add_option('--config_file', type='string', dest='config_file', + action='store', + help='Name of the configuration file.') + + def _PerformDownloadOptions(self, parser): + """Adds 'download_data' specific options to the 'parser' passed in. + + Args: + parser: An instance of OptionsParser. + """ + self._PerformLoadOptions(parser) + parser.add_option('--exporter_opts', type='string', dest='exporter_opts', + help='A string to pass to the Exporter.initialize method.' + ) + parser.add_option('--result_db_filename', type='string', + dest='result_db_filename', + action='store', + help='Database to write entities to for download.') + parser.add_option('--config_file', type='string', dest='config_file', + action='store', + help='Name of the configuration file.') + + def _CreateBulkloadConfigOptions(self, parser): + """Adds 'download_data' specific options to the 'parser' passed in. + + Args: + parser: An instance of OptionsParser. + """ + self._PerformLoadOptions(parser) + + class Action(object): + """Contains information about a command line action. + + Attributes: + function: The name of a function defined on AppCfg or its subclasses + that will perform the appropriate action. + usage: A command line usage string. + short_desc: A one-line description of the action. + long_desc: A detailed description of the action. Whitespace and + formatting will be preserved. + options: A function that will add extra options to a given OptionParser + object. + """ + + def __init__(self, function, usage, short_desc, long_desc='', + options=lambda obj, parser: None): + """Initializer for the class attributes.""" + self.function = function + self.usage = usage + self.short_desc = short_desc + self.long_desc = long_desc + self.options = options + + def __call__(self, appcfg): + """Invoke this Action on the specified AppCfg. + + This calls the function of the appropriate name on AppCfg, and + respects polymophic overrides. + + Args: + appcfg: The appcfg to use. + Returns: + The result of the function call. + """ + method = getattr(appcfg, self.function) + return method() + + actions = { + + 'help': Action( + function='Help', + usage='%prog help ', + short_desc='Print help for a specific action.'), + + 'update': Action( + function='Update', + usage='%prog [options] update ', + options=_UpdateOptions, + short_desc='Create or update an app version.', + long_desc=""" +Specify a directory that contains all of the files required by +the app, and appcfg.py will create/update the app version referenced +in the app.yaml file at the top level of that directory. appcfg.py +will follow symlinks and recursively upload all files to the server. +Temporary or source control files (e.g. foo~, .svn/*) will be skipped."""), + + 'update_cron': Action( + function='UpdateCron', + usage='%prog [options] update_cron ', + short_desc='Update application cron definitions.', + long_desc=""" +The 'update_cron' command will update any new, removed or changed cron +definitions from the optional cron.yaml file."""), + + 'update_indexes': Action( + function='UpdateIndexes', + usage='%prog [options] update_indexes ', + short_desc='Update application indexes.', + long_desc=""" +The 'update_indexes' command will add additional indexes which are not currently +in production as well as restart any indexes that were not completed."""), + + 'update_queues': Action( + function='UpdateQueues', + usage='%prog [options] update_queues ', + short_desc='Update application task queue definitions.', + long_desc=""" +The 'update_queue' command will update any new, removed or changed task queue +definitions from the optional queue.yaml file."""), + + 'update_dos': Action( + function='UpdateDos', + usage='%prog [options] update_dos ', + short_desc='Update application dos definitions.', + long_desc=""" +The 'update_dos' command will update any new, removed or changed dos +definitions from the optional dos.yaml file."""), + + 'vacuum_indexes': Action( + function='VacuumIndexes', + usage='%prog [options] vacuum_indexes ', + options=_VacuumIndexesOptions, + short_desc='Delete unused indexes from application.', + long_desc=""" +The 'vacuum_indexes' command will help clean up indexes which are no longer +in use. It does this by comparing the local index configuration with +indexes that are actually defined on the server. If any indexes on the +server do not exist in the index configuration file, the user is given the +option to delete them."""), + + 'rollback': Action( + function='Rollback', + usage='%prog [options] rollback ', + short_desc='Rollback an in-progress update.', + long_desc=""" +The 'update' command requires a server-side transaction. Use 'rollback' +if you get an error message about another transaction being in progress +and you are sure that there is no such transaction."""), + + 'request_logs': Action( + function='RequestLogs', + usage='%prog [options] request_logs ', + options=_RequestLogsOptions, + short_desc='Write request logs in Apache common log format.', + long_desc=""" +The 'request_logs' command exports the request logs from your application +to a file. It will write Apache common log format records ordered +chronologically. If output file is '-' stdout will be written."""), + + 'cron_info': Action( + function='CronInfo', + usage='%prog [options] cron_info ', + options=_CronInfoOptions, + short_desc='Display information about cron jobs.', + long_desc=""" +The 'cron_info' command will display the next 'number' runs (default 5) for +each cron job defined in the cron.yaml file."""), + + 'upload_data': Action( + function='PerformUpload', + usage='%prog [options] upload_data ', + options=_PerformUploadOptions, + short_desc='Upload data records to datastore.', + long_desc=""" +The 'upload_data' command translates input records into datastore entities and +uploads them into your application's datastore."""), + + 'download_data': Action( + function='PerformDownload', + usage='%prog [options] download_data ', + options=_PerformDownloadOptions, + short_desc='Download entities from datastore.', + long_desc=""" +The 'download_data' command downloads datastore entities and writes them to +file as CSV or developer defined format."""), + + 'create_bulkloader_config': Action( + function='CreateBulkloadConfig', + usage='%prog [options] create_bulkload_config ', + options=_CreateBulkloadConfigOptions, + short_desc='Create a bulkloader.yaml from a running application.', + long_desc=""" +The 'create_bulkloader_config' command creates a bulkloader.yaml configuration +template for use with upload_data or download_data."""), + + 'set_default_version': Action( + function='SetDefaultVersion', + usage='%prog [options] set_default_version ', + short_desc='Set the default (serving) version.', + long_desc=""" +The 'set_default_version' command sets the default (serving) version of the app. +Defaults to using the version specified in app.yaml; use the --version flag to +override this."""), + + + + } + + +def main(argv): + logging.basicConfig(format=('%(asctime)s %(levelname)s %(filename)s:' + '%(lineno)s %(message)s ')) + try: + result = AppCfgApp(argv).Run() + if result: + sys.exit(result) + except KeyboardInterrupt: + StatusUpdate('Interrupted.') + sys.exit(1) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/google_appengine/google/appengine/tools/appcfg.pyc b/google_appengine/google/appengine/tools/appcfg.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a857bc62d964bb2d25785676f321f734a2291892 GIT binary patch literal 103418 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/appengine_rpc.py b/google_appengine/google/appengine/tools/appengine_rpc.py new file mode 100755 index 0000000..2ec2d27 --- /dev/null +++ b/google_appengine/google/appengine/tools/appengine_rpc.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Tool for performing authenticated RPCs against App Engine.""" + + +import google + +import cookielib +import fancy_urllib +import logging +import os +import re +import socket +import sys +import urllib +import urllib2 + +from google.appengine.tools import dev_appserver_login + + +logger = logging.getLogger('google.appengine.tools.appengine_rpc') + +def GetPlatformToken(os_module=os, sys_module=sys, platform=sys.platform): + """Returns a 'User-agent' token for the host system platform. + + Args: + os_module, sys_module, platform: Used for testing. + + Returns: + String containing the platform token for the host system. + """ + if hasattr(sys_module, "getwindowsversion"): + windows_version = sys_module.getwindowsversion() + version_info = ".".join(str(i) for i in windows_version[:4]) + return platform + "/" + version_info + elif hasattr(os_module, "uname"): + uname = os_module.uname() + return "%s/%s" % (uname[0], uname[2]) + else: + return "unknown" + +def HttpRequestToString(req, include_data=True): + """Converts a urllib2.Request to a string. + + Args: + req: urllib2.Request + Returns: + Multi-line string representing the request. + """ + + headers = "" + for header in req.header_items(): + headers += "%s: %s\n" % (header[0], header[1]) + + template = ("%(method)s %(selector)s %(type)s/1.1\n" + "Host: %(host)s\n" + "%(headers)s") + if include_data: + template = template + "\n%(data)s" + + return template % { + 'method' : req.get_method(), + 'selector' : req.get_selector(), + 'type' : req.get_type().upper(), + 'host' : req.get_host(), + 'headers': headers, + 'data': req.get_data(), + } + +class ClientLoginError(urllib2.HTTPError): + """Raised to indicate there was an error authenticating with ClientLogin.""" + + def __init__(self, url, code, msg, headers, args): + urllib2.HTTPError.__init__(self, url, code, msg, headers, None) + self.args = args + self.reason = args["Error"] + + def read(self): + return '%d %s: %s' % (self.code, self.msg, self.reason) + + +class AbstractRpcServer(object): + """Provides a common interface for a simple RPC server.""" + + def __init__(self, host, auth_function, user_agent, source, + host_override=None, extra_headers=None, save_cookies=False, + auth_tries=3, account_type=None, debug_data=True, secure=True): + """Creates a new HttpRpcServer. + + Args: + host: The host to send requests to. + auth_function: A function that takes no arguments and returns an + (email, password) tuple when called. Will be called if authentication + is required. + user_agent: The user-agent string to send to the server. Specify None to + omit the user-agent header. + source: The source to specify in authentication requests. + host_override: The host header to send to the server (defaults to host). + extra_headers: A dict of extra headers to append to every request. Values + supplied here will override other default headers that are supplied. + save_cookies: If True, save the authentication cookies to local disk. + If False, use an in-memory cookiejar instead. Subclasses must + implement this functionality. Defaults to False. + auth_tries: The number of times to attempt auth_function before failing. + account_type: One of GOOGLE, HOSTED_OR_GOOGLE, or None for automatic. + debug_data: Whether debugging output should include data contents. + """ + if secure: + self.scheme = "https" + else: + self.scheme = "http" + self.host = host + self.host_override = host_override + self.auth_function = auth_function + self.source = source + self.authenticated = False + self.auth_tries = auth_tries + self.debug_data = debug_data + + self.account_type = account_type + + self.extra_headers = {} + if user_agent: + self.extra_headers["User-Agent"] = user_agent + if extra_headers: + self.extra_headers.update(extra_headers) + + self.save_cookies = save_cookies + self.cookie_jar = cookielib.MozillaCookieJar() + self.opener = self._GetOpener() + if self.host_override: + logger.info("Server: %s; Host: %s", self.host, self.host_override) + else: + logger.info("Server: %s", self.host) + + if ((self.host_override and self.host_override == "localhost") or + self.host == "localhost" or self.host.startswith("localhost:")): + self._DevAppServerAuthenticate() + + def _GetOpener(self): + """Returns an OpenerDirector for making HTTP requests. + + Returns: + A urllib2.OpenerDirector object. + """ + raise NotImplemented() + + def _CreateRequest(self, url, data=None): + """Creates a new urllib request.""" + req = fancy_urllib.FancyRequest(url, data=data) + if self.host_override: + req.add_header("Host", self.host_override) + for key, value in self.extra_headers.iteritems(): + req.add_header(key, value) + return req + + def _GetAuthToken(self, email, password): + """Uses ClientLogin to authenticate the user, returning an auth token. + + Args: + email: The user's email address + password: The user's password + + Raises: + ClientLoginError: If there was an error authenticating with ClientLogin. + HTTPError: If there was some other form of HTTP error. + + Returns: + The authentication token returned by ClientLogin. + """ + account_type = self.account_type + if not account_type: + if (self.host.split(':')[0].endswith(".google.com") + or (self.host_override + and self.host_override.split(':')[0].endswith(".google.com"))): + account_type = "HOSTED_OR_GOOGLE" + else: + account_type = "GOOGLE" + data = { + "Email": email, + "Passwd": password, + "service": "ah", + "source": self.source, + "accountType": account_type + } + + req = self._CreateRequest( + url="https://www.google.com/accounts/ClientLogin", + data=urllib.urlencode(data)) + try: + response = self.opener.open(req) + response_body = response.read() + response_dict = dict(x.split("=") + for x in response_body.split("\n") if x) + return response_dict["Auth"] + except urllib2.HTTPError, e: + if e.code == 403: + body = e.read() + response_dict = dict(x.split("=", 1) for x in body.split("\n") if x) + raise ClientLoginError(req.get_full_url(), e.code, e.msg, + e.headers, response_dict) + else: + raise + + def _GetAuthCookie(self, auth_token): + """Fetches authentication cookies for an authentication token. + + Args: + auth_token: The authentication token returned by ClientLogin. + + Raises: + HTTPError: If there was an error fetching the authentication cookies. + """ + continue_location = "http://localhost/" + args = {"continue": continue_location, "auth": auth_token} + login_path = os.environ.get("APPCFG_LOGIN_PATH", "/_ah") + req = self._CreateRequest("%s://%s%s/login?%s" % + (self.scheme, self.host, login_path, + urllib.urlencode(args))) + try: + response = self.opener.open(req) + except urllib2.HTTPError, e: + response = e + if (response.code != 302 or + response.info()["location"] != continue_location): + raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, + response.headers, response.fp) + self.authenticated = True + + def _Authenticate(self): + """Authenticates the user. + + The authentication process works as follows: + 1) We get a username and password from the user + 2) We use ClientLogin to obtain an AUTH token for the user + (see http://code.google.com/apis/accounts/AuthForInstalledApps.html). + 3) We pass the auth token to /_ah/login on the server to obtain an + authentication cookie. If login was successful, it tries to redirect + us to the URL we provided. + + If we attempt to access the upload API without first obtaining an + authentication cookie, it returns a 401 response and directs us to + authenticate ourselves with ClientLogin. + """ + for unused_i in range(self.auth_tries): + credentials = self.auth_function() + try: + auth_token = self._GetAuthToken(credentials[0], credentials[1]) + except ClientLoginError, e: + if e.reason == "BadAuthentication": + print >>sys.stderr, "Invalid username or password." + continue + if e.reason == "CaptchaRequired": + print >>sys.stderr, ( + "Please go to\n" + "https://www.google.com/accounts/DisplayUnlockCaptcha\n" + "and verify you are a human. Then try again.") + break + if e.reason == "NotVerified": + print >>sys.stderr, "Account not verified." + break + if e.reason == "TermsNotAgreed": + print >>sys.stderr, "User has not agreed to TOS." + break + if e.reason == "AccountDeleted": + print >>sys.stderr, "The user account has been deleted." + break + if e.reason == "AccountDisabled": + print >>sys.stderr, "The user account has been disabled." + break + if e.reason == "ServiceDisabled": + print >>sys.stderr, ("The user's access to the service has been " + "disabled.") + break + if e.reason == "ServiceUnavailable": + print >>sys.stderr, "The service is not available; try again later." + break + raise + self._GetAuthCookie(auth_token) + return + + def _DevAppServerAuthenticate(self): + """Authenticates the user on the dev_appserver.""" + credentials = self.auth_function() + value = dev_appserver_login.CreateCookieData(credentials[0], True) + self.extra_headers["Cookie"] = ('dev_appserver_login="%s"; Path=/;' % value) + + def Send(self, request_path, payload="", + content_type="application/octet-stream", + timeout=None, + **kwargs): + """Sends an RPC and returns the response. + + Args: + request_path: The path to send the request to, eg /api/appversion/create. + payload: The body of the request, or None to send an empty request. + content_type: The Content-Type header to use. + timeout: timeout in seconds; default None i.e. no timeout. + (Note: for large requests on OS X, the timeout doesn't work right.) + kwargs: Any keyword arguments are converted into query string parameters. + + Returns: + The response body, as a string. + """ + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + tries = 0 + auth_tried = False + while True: + tries += 1 + args = dict(kwargs) + url = "%s://%s%s?%s" % (self.scheme, self.host, request_path, + urllib.urlencode(args)) + req = self._CreateRequest(url=url, data=payload) + req.add_header("Content-Type", content_type) + req.add_header("X-appcfg-api-version", "1") + try: + logger.debug('Sending HTTP request:\n%s', + HttpRequestToString(req, include_data=self.debug_data)) + f = self.opener.open(req) + response = f.read() + f.close() + return response + except urllib2.HTTPError, e: + logger.debug("Got http error, this is try #%s", tries) + if tries > self.auth_tries: + raise + elif e.code == 401: + if auth_tried: + raise + auth_tried = True + self._Authenticate() + elif e.code >= 500 and e.code < 600: + continue + elif e.code == 302: + if auth_tried: + raise + auth_tried = True + loc = e.info()["location"] + logger.debug("Got 302 redirect. Location: %s", loc) + if loc.startswith("https://www.google.com/accounts/ServiceLogin"): + self._Authenticate() + elif re.match(r"https://www.google.com/a/[a-z0-9.-]+/ServiceLogin", + loc): + self.account_type = os.getenv("APPENGINE_RPC_HOSTED_LOGIN_TYPE", + "HOSTED") + self._Authenticate() + elif loc.startswith("http://%s/_ah/login" % (self.host,)): + self._DevAppServerAuthenticate() + else: + raise + else: + raise + finally: + socket.setdefaulttimeout(old_timeout) + + +class HttpRpcServer(AbstractRpcServer): + """Provides a simplified RPC-style interface for HTTP requests.""" + + DEFAULT_COOKIE_FILE_PATH = "~/.appcfg_cookies" + + def __init__(self, *args, **kwargs): + self.certpath = os.path.normpath(os.path.join( + os.path.dirname(__file__), '..', '..', '..', 'lib', 'cacerts', + 'cacerts.txt')) + self.cert_file_available = os.path.exists(self.certpath) + super(HttpRpcServer, self).__init__(*args, **kwargs) + + def _CreateRequest(self, url, data=None): + """Creates a new urllib request.""" + req = super(HttpRpcServer, self)._CreateRequest(url, data) + if self.cert_file_available and fancy_urllib.can_validate_certs(): + req.set_ssl_info(ca_certs=self.certpath) + return req + + def _Authenticate(self): + """Save the cookie jar after authentication.""" + if self.cert_file_available and not fancy_urllib.can_validate_certs(): + logger.warn("""ssl module not found. +Without the ssl module, the identity of the remote host cannot be verified, and +connections may NOT be secure. To fix this, please install the ssl module from +http://pypi.python.org/pypi/ssl . +To learn more, see http://code.google.com/appengine/kb/general.html#rpcssl .""") + super(HttpRpcServer, self)._Authenticate() + if self.cookie_jar.filename is not None and self.save_cookies: + logger.info("Saving authentication cookies to %s", + self.cookie_jar.filename) + self.cookie_jar.save() + + def _GetOpener(self): + """Returns an OpenerDirector that supports cookies and ignores redirects. + + Returns: + A urllib2.OpenerDirector object. + """ + opener = urllib2.OpenerDirector() + opener.add_handler(fancy_urllib.FancyProxyHandler()) + opener.add_handler(urllib2.UnknownHandler()) + opener.add_handler(urllib2.HTTPHandler()) + opener.add_handler(urllib2.HTTPDefaultErrorHandler()) + opener.add_handler(fancy_urllib.FancyHTTPSHandler()) + opener.add_handler(urllib2.HTTPErrorProcessor()) + + if self.save_cookies: + self.cookie_jar.filename = os.path.expanduser( + HttpRpcServer.DEFAULT_COOKIE_FILE_PATH) + + if os.path.exists(self.cookie_jar.filename): + try: + self.cookie_jar.load() + self.authenticated = True + logger.info("Loaded authentication cookies from %s", + self.cookie_jar.filename) + except (OSError, IOError, cookielib.LoadError), e: + logger.debug("Could not load authentication cookies; %s: %s", + e.__class__.__name__, e) + self.cookie_jar.filename = None + else: + try: + fd = os.open(self.cookie_jar.filename, os.O_CREAT, 0600) + os.close(fd) + except (OSError, IOError), e: + logger.debug("Could not create authentication cookies file; %s: %s", + e.__class__.__name__, e) + self.cookie_jar.filename = None + + opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar)) + return opener diff --git a/google_appengine/google/appengine/tools/appengine_rpc.pyc b/google_appengine/google/appengine/tools/appengine_rpc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b616bde45740bc5318af5a7fe469e34f0eb8b7e9 GIT binary patch literal 17426 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 XRcqkao+ymlXQr#?=BhJ4RWttw3#_W2 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/bulkload_client.py b/google_appengine/google/appengine/tools/bulkload_client.py new file mode 100755 index 0000000..bec0fde --- /dev/null +++ b/google_appengine/google/appengine/tools/bulkload_client.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Imports CSV data over HTTP. + +Usage: + %s [flags] + + --debug Show debugging information. (Optional) + --cookie= Whole Cookie header to supply to the server, including + the parameter name (e.g., "ACSID=..."). (Optional) + --url= URL endpoint to post to for importing data. (Required) + --batch_size= Number of Entity objects to include in each post to + the URL endpoint. The more data per row/Entity, the + smaller the batch size should be. (Default 10) + --filename= Path to the CSV file to import. (Required) + --kind= Name of the Entity object kind to put in the datastore. + (Required) + +The exit status will be 0 on success, non-zero on import failure. + +Works with the bulkload mix-in library for google.appengine.ext.bulkload. +Please look there for documentation about how to setup the server side. +""" + + +import StringIO +import httplib +import logging +import csv +import getopt +import socket +import sys +import urllib +import urlparse + +from google.appengine.ext.bulkload import constants + + + +class Error(Exception): + """Base-class for exceptions in this module.""" + + +class PostError(Error): + """An error has occured while trying to post data to the server.""" + + +class BadServerStatusError(PostError): + """The server has returned an error while importing data.""" + + +def ContentGenerator(csv_file, + batch_size, + create_csv_reader=csv.reader, + create_csv_writer=csv.writer): + """Retrieves CSV data up to a batch size at a time. + + Args: + csv_file: A file-like object for reading CSV data. + batch_size: Maximum number of CSV rows to yield on each iteration. + create_csv_reader, create_csv_writer: Used for dependency injection. + + Yields: + Tuple (entity_count, csv_content) where: + entity_count: Number of entities contained in the csv_content. Will be + less than or equal to the batch_size and greater than 0. + csv_content: String containing the CSV content containing the next + entity_count entities. + """ + try: + csv.field_size_limit(800000) + except AttributeError: + pass + + reader = create_csv_reader(csv_file, skipinitialspace=True) + exhausted = False + + while not exhausted: + rows_written = 0 + content = StringIO.StringIO() + writer = create_csv_writer(content) + try: + for i in xrange(batch_size): + row = reader.next() + writer.writerow(row) + rows_written += 1 + except StopIteration: + exhausted = True + + if rows_written > 0: + yield rows_written, content.getvalue() + + +def PostEntities(host_port, uri, cookie, kind, content): + """Posts Entity records to a remote endpoint over HTTP. + + Args: + host_port: String containing the "host:port" pair; the port is optional. + uri: Relative URI to access on the remote host (e.g., '/bulkload'). + cookie: String containing the Cookie header to use, if any. + kind: Kind of the Entity records being posted. + content: String containing the CSV data for the entities. + + Raises: + BadServerStatusError if the server was contactable but returns an error. + PostError If an error occurred while connecting to the server or reading + or writing data. + """ + logging.debug('Connecting to %s', host_port) + try: + body = urllib.urlencode({ + constants.KIND_PARAM: kind, + constants.CSV_PARAM: content, + }) + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': len(body), + 'Cookie': cookie, + } + + logging.debug('Posting %d bytes to http://%s%s', len(body), host_port, uri) + connection = httplib.HTTPConnection(host_port) + try: + connection.request('POST', uri, body, headers) + response = connection.getresponse() + + status = response.status + reason = response.reason + content = response.read() + logging.debug('Received response code %d: %s', status, reason) + if status != httplib.OK: + raise BadServerStatusError('Received code %d: %s\n%s' % ( + status, reason, content)) + finally: + connection.close() + except (IOError, httplib.HTTPException, socket.error), e: + logging.debug('Encountered exception accessing HTTP server: %s', e) + raise PostError(e) + + +def SplitURL(url): + """Splits an HTTP URL into pieces. + + Args: + url: String containing a full URL string (e.g., + 'http://blah.com:8080/stuff?param=1#foo') + + Returns: + Tuple (netloc, uri) where: + netloc: String containing the host/port combination from the URL. The + port is optional. (e.g., 'blah.com:8080'). + uri: String containing the relative URI of the URL. (e.g., '/stuff'). + """ + scheme, netloc, path, query, fragment = urlparse.urlsplit(url) + return netloc, path + + +def ImportCSV(filename, + post_url, + cookie, + batch_size, + kind, + split_url=SplitURL, + openfile=file, + create_content_generator=ContentGenerator, + post_entities=PostEntities): + """Imports CSV data using a series of HTTP posts. + + Args: + filename: File on disk containing CSV data. + post_url: URL to post the Entity data to. + cookie: Full cookie header to use while connecting. + batch_size: Maximum number of Entity objects to post with each request. + kind: Entity kind of the objects being posted. + split_url, openfile, create_content_generator, post_entities: Used for + dependency injection. + + Returns: + True if all entities were imported successfully; False otherwise. + """ + host_port, uri = split_url(post_url) + csv_file = openfile(filename, 'r') + try: + content_gen = create_content_generator(csv_file, batch_size) + logging.info('Starting import; maximum %d entities per post', batch_size) + for num_entities, content in content_gen: + logging.info('Importing %d entities in %d bytes', + num_entities, len(content)) + try: + content = post_entities(host_port, uri, cookie, kind, content) + except PostError, e: + logging.error('An error occurred while importing: %s', e) + return False + finally: + csv_file.close() + return True + + +def PrintUsageExit(code): + """Prints usage information and exits with a status code. + + Args: + code: Status code to pass to sys.exit() after displaying usage information. + """ + print sys.modules['__main__'].__doc__ % sys.argv[0] + sys.stdout.flush() + sys.stderr.flush() + sys.exit(code) + + +def ParseArguments(argv): + """Parses command-line arguments. + + Prints out a help message if -h or --help is supplied. + + Args: + argv: List of command-line arguments. + + Returns: + Tuple (url, filename, cookie, batch_size, kind) containing the values from + each corresponding command-line flag. + """ + opts, args = getopt.getopt( + argv[1:], + 'h', + ['debug', + 'help', + 'url=', + 'filename=', + 'cookie=', + 'batch_size=', + 'kind=']) + + url = None + filename = None + cookie = '' + batch_size = 10 + kind = None + encoding = None + + for option, value in opts: + if option == '--debug': + logging.getLogger().setLevel(logging.DEBUG) + if option in ('-h', '--help'): + PrintUsageExit(0) + if option == '--url': + url = value + if option == '--filename': + filename = value + if option == '--cookie': + cookie = value + if option == '--batch_size': + batch_size = int(value) + if batch_size <= 0: + print >>sys.stderr, 'batch_size must be 1 or larger' + PrintUsageExit(1) + if option == '--kind': + kind = value + + return (url, filename, cookie, batch_size, kind) + + +def main(argv): + """Runs the importer.""" + logging.basicConfig( + level=logging.INFO, + format='%(levelname)-8s %(asctime)s %(filename)s] %(message)s') + + args = ParseArguments(argv) + if [arg for arg in args if arg is None]: + print >>sys.stderr, 'Invalid arguments' + PrintUsageExit(1) + + url, filename, cookie, batch_size, kind = args + if ImportCSV(filename, url, cookie, batch_size, kind): + logging.info('Import succcessful') + return 0 + logging.error('Import failed') + return 1 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/google_appengine/google/appengine/tools/bulkloader.py b/google_appengine/google/appengine/tools/bulkloader.py new file mode 100755 index 0000000..15bf1a7 --- /dev/null +++ b/google_appengine/google/appengine/tools/bulkloader.py @@ -0,0 +1,4033 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Imports data over HTTP. + +Usage: + %(arg0)s [flags] + + --debug Show debugging information. (Optional) + --application= Application ID of endpoint (Optional for + *.appspot.com) + --auth_domain= The auth domain to use for logging in and for + UserProperties. (Default: gmail.com) + --bandwidth_limit= The maximum number of bytes per second for the + aggregate transfer of data to the server. Bursts + may exceed this, but overall transfer rate is + restricted to this rate. (Default 250000) + --batch_size= Number of Entity objects to include in each post to + the URL endpoint. The more data per row/Entity, the + smaller the batch size should be. (Default 10) + --config_file= File containing Model and Loader definitions or + bulkloader.yaml transforms. (Required unless --dump, + --restore, or --create_config are used.) + --create_config Write a bulkloader.yaml configuration file to + --filename based on the server side datastore state. + --db_filename= Specific progress database to write to, or to + resume from. If not supplied, then a new database + will be started, named: + bulkloader-progress-TIMESTAMP. + The special filename "skip" may be used to simply + skip reading/writing any progress information. + --download Export entities to a file. + --dry_run Do not execute any remote_api calls. + --dump Use zero-configuration dump format. + --email= The username to use. Will prompt if omitted. + --exporter_opts= + A string to pass to the Exporter.initialize method. + --filename= Path to the file to import. (Required when + importing or exporting, not mapping.) + --has_header Skip the first row of the input. + --http_limit= The maximum numer of HTTP requests per second to + send to the server. (Default: 8) + --kind= Name of the Entity object kind to put in the + datastore. (Required) + --loader_opts= A string to pass to the Loader.initialize method. + --log_file= File to write bulkloader logs. If not supplied + then a new log file will be created, named: + bulkloader-log-TIMESTAMP. + --map Map an action across datastore entities. + --mapper_opts= A string to pass to the Mapper.Initialize method. + --num_threads= Number of threads to use for uploading entities + (Default 10) + --passin Read the login password from stdin. + --restore Restore from zero-configuration dump format. + --result_db_filename= + Result database to write to for downloads. + --rps_limit= The maximum number of records per second to + transfer to the server. (Default: 20) + --url= URL endpoint to post to for importing data. + (Required) + --namespace= Use specified namespace instead of the default one + for all datastore operations. + +The exit status will be 0 on success, non-zero on import failure. + +Works with the remote_api mix-in library for google.appengine.ext.remote_api. +Please look there for documentation about how to setup the server side. + +Example: + +%(arg0)s --url=http://app.appspot.com/remote_api --kind=Model \ + --filename=data.csv --config_file=loader_config.py + +""" + + + +import csv +import errno +import getopt +import getpass +import imp +import logging +import os +import Queue +import re +import shutil +import signal +import StringIO +import sys +import threading +import time +import traceback +import urllib2 +import urlparse + +from google.appengine.datastore import entity_pb + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api.namespace_manager import namespace_manager +from google.appengine.datastore import datastore_pb +from google.appengine.ext import db +from google.appengine.ext import key_range as key_range_module +from google.appengine.ext.bulkload import bulkloader_config +from google.appengine.ext.db import polymodel +from google.appengine.ext.db import stats +from google.appengine.ext.remote_api import remote_api_stub +from google.appengine.ext.remote_api import throttle as remote_api_throttle +from google.appengine.runtime import apiproxy_errors +from google.appengine.tools import adaptive_thread_pool +from google.appengine.tools import appengine_rpc +from google.appengine.tools.requeue import ReQueue + +try: + import sqlite3 +except ImportError: + pass + +logger = logging.getLogger('google.appengine.tools.bulkloader') + +KeyRange = key_range_module.KeyRange + +DEFAULT_THREAD_COUNT = 10 + +DEFAULT_BATCH_SIZE = 10 + +DEFAULT_DOWNLOAD_BATCH_SIZE = 100 + +DEFAULT_QUEUE_SIZE = DEFAULT_THREAD_COUNT * 10 + +_THREAD_SHOULD_EXIT = '_THREAD_SHOULD_EXIT' + +STATE_READ = 0 +STATE_SENDING = 1 +STATE_SENT = 2 +STATE_NOT_SENT = 3 + +STATE_GETTING = 1 +STATE_GOT = 2 +STATE_ERROR = 3 + +DATA_CONSUMED_TO_HERE = 'DATA_CONSUMED_TO_HERE' + +INITIAL_BACKOFF = 1.0 + +BACKOFF_FACTOR = 2.0 + + +DEFAULT_BANDWIDTH_LIMIT = 250000 + +DEFAULT_RPS_LIMIT = 20 + +DEFAULT_REQUEST_LIMIT = 8 + +MAXIMUM_INCREASE_DURATION = 5.0 +MAXIMUM_HOLD_DURATION = 12.0 + + +def ImportStateMessage(state): + """Converts a numeric state identifier to a status message.""" + return ({ + STATE_READ: 'Batch read from file.', + STATE_SENDING: 'Sending batch to server.', + STATE_SENT: 'Batch successfully sent.', + STATE_NOT_SENT: 'Error while sending batch.' + }[state]) + + +def ExportStateMessage(state): + """Converts a numeric state identifier to a status message.""" + return ({ + STATE_READ: 'Batch read from file.', + STATE_GETTING: 'Fetching batch from server', + STATE_GOT: 'Batch successfully fetched.', + STATE_ERROR: 'Error while fetching batch' + }[state]) + + +def MapStateMessage(state): + """Converts a numeric state identifier to a status message.""" + return ({ + STATE_READ: 'Batch read from file.', + STATE_GETTING: 'Querying for batch from server', + STATE_GOT: 'Batch successfully fetched.', + STATE_ERROR: 'Error while fetching or mapping.' + }[state]) + + +def ExportStateName(state): + """Converts a numeric state identifier to a string.""" + return ({ + STATE_READ: 'READ', + STATE_GETTING: 'GETTING', + STATE_GOT: 'GOT', + STATE_ERROR: 'NOT_GOT' + }[state]) + + +def ImportStateName(state): + """Converts a numeric state identifier to a string.""" + return ({ + STATE_READ: 'READ', + STATE_GETTING: 'SENDING', + STATE_GOT: 'SENT', + STATE_NOT_SENT: 'NOT_SENT' + }[state]) + + +class Error(Exception): + """Base-class for exceptions in this module.""" + + +class MissingPropertyError(Error): + """An expected field is missing from an entity, and no default was given.""" + + +class FatalServerError(Error): + """An unrecoverable error occurred while posting data to the server.""" + + +class ResumeError(Error): + """Error while trying to resume a partial upload.""" + + +class ConfigurationError(Error): + """Error in configuration options.""" + + +class AuthenticationError(Error): + """Error while trying to authenticate with the server.""" + + +class FileNotFoundError(Error): + """A filename passed in by the user refers to a non-existent input file.""" + + +class FileNotReadableError(Error): + """A filename passed in by the user refers to a non-readable input file.""" + + +class FileExistsError(Error): + """A filename passed in by the user refers to an existing output file.""" + + +class FileNotWritableError(Error): + """A filename passed in by the user refers to a non-writable output file.""" + + +class BadStateError(Error): + """A work item in an unexpected state was encountered.""" + + +class KeyRangeError(Error): + """An error during construction of a KeyRangeItem.""" + + +class KindStatError(Error): + """Unable to find kind stats for an all-kinds download.""" + + +class FieldSizeLimitError(Error): + """The csv module tried to read a field larger than the size limit.""" + + def __init__(self, limit): + self.message = """ +A field in your CSV input file has exceeded the current limit of %d. + +You can raise this limit by adding the following lines to your config file: + +import csv +csv.field_size_limit(new_limit) + +where new_limit is number larger than the size in bytes of the largest +field in your CSV. +""" % limit + Error.__init__(self, self.message) + + +class NameClashError(Error): + """A name clash occurred while trying to alias old method names.""" + + def __init__(self, old_name, new_name, klass): + Error.__init__(self, old_name, new_name, klass) + self.old_name = old_name + self.new_name = new_name + self.klass = klass + + +def GetCSVGeneratorFactory(kind, csv_filename, batch_size, csv_has_header, + openfile=open, create_csv_reader=csv.reader): + """Return a factory that creates a CSV-based UploadWorkItem generator. + + Args: + kind: The kind of the entities being uploaded. + csv_filename: File on disk containing CSV data. + batch_size: Maximum number of CSV rows to stash into an UploadWorkItem. + csv_has_header: Whether to skip the first row of the CSV. + openfile: Used for dependency injection. + create_csv_reader: Used for dependency injection. + + Returns: + A callable (accepting the Progress Queue and Progress Generators + as input) which creates the UploadWorkItem generator. + """ + loader = Loader.RegisteredLoader(kind) + loader._Loader__openfile = openfile + loader._Loader__create_csv_reader = create_csv_reader + record_generator = loader.generate_records(csv_filename) + + def CreateGenerator(request_manager, progress_queue, progress_generator, + unused_kinds): + """Initialize a UploadWorkItem generator. + + Args: + request_manager: A RequestManager instance. + progress_queue: A ProgressQueue instance to send progress information. + progress_generator: A generator of progress information or None. + unused_kinds: The kinds being generated (ignored in this method). + + Returns: + An UploadWorkItemGenerator instance. + """ + return UploadWorkItemGenerator(request_manager, + progress_queue, + progress_generator, + record_generator, + csv_has_header, + batch_size) + + return CreateGenerator + + +class UploadWorkItemGenerator(object): + """Reads rows from a row generator and generates UploadWorkItems.""" + + def __init__(self, + request_manager, + progress_queue, + progress_generator, + record_generator, + skip_first, + batch_size): + """Initialize a WorkItemGenerator. + + Args: + request_manager: A RequestManager instance with which to associate + WorkItems. + progress_queue: A progress queue with which to associate WorkItems. + progress_generator: A generator of progress information. + record_generator: A generator of data records. + skip_first: Whether to skip the first data record. + batch_size: The number of data records per WorkItem. + """ + self.request_manager = request_manager + self.progress_queue = progress_queue + self.progress_generator = progress_generator + self.reader = record_generator + self.skip_first = skip_first + self.batch_size = batch_size + self.line_number = 1 + self.column_count = None + self.read_rows = [] + self.row_count = 0 + self.xfer_count = 0 + + def _AdvanceTo(self, line): + """Advance the reader to the given line. + + Args: + line: A line number to advance to. + """ + while self.line_number < line: + self.reader.next() + self.line_number += 1 + self.row_count += 1 + self.xfer_count += 1 + + def _ReadRows(self, key_start, key_end): + """Attempts to read and encode rows [key_start, key_end]. + + The encoded rows are stored in self.read_rows. + + Args: + key_start: The starting line number. + key_end: The ending line number. + + Raises: + StopIteration: if the reader runs out of rows + ResumeError: if there are an inconsistent number of columns. + """ + assert self.line_number == key_start + self.read_rows = [] + while self.line_number <= key_end: + row = self.reader.next() + self.row_count += 1 + if self.column_count is None: + self.column_count = len(row) + self.read_rows.append((self.line_number, row)) + self.line_number += 1 + + def _MakeItem(self, key_start, key_end, rows, progress_key=None): + """Makes a UploadWorkItem containing the given rows, with the given keys. + + Args: + key_start: The start key for the UploadWorkItem. + key_end: The end key for the UploadWorkItem. + rows: A list of the rows for the UploadWorkItem. + progress_key: The progress key for the UploadWorkItem + + Returns: + An UploadWorkItem instance for the given batch. + """ + assert rows + + item = UploadWorkItem(self.request_manager, self.progress_queue, rows, + key_start, key_end, progress_key=progress_key) + + return item + + def Batches(self): + """Reads from the record_generator and generates UploadWorkItems. + + Yields: + Instances of class UploadWorkItem + + Raises: + ResumeError: If the progress database and data file indicate a different + number of rows. + """ + if self.skip_first: + logger.info('Skipping header line.') + try: + self.reader.next() + except StopIteration: + return + + exhausted = False + + self.line_number = 1 + self.column_count = None + + logger.info('Starting import; maximum %d entities per post', + self.batch_size) + + state = None + if self.progress_generator: + for progress_key, kind, state, key_start, key_end in ( + self.progress_generator): + if key_start: + try: + self._AdvanceTo(key_start) + self._ReadRows(key_start, key_end) + yield self._MakeItem(key_start, + key_end, + self.read_rows, + progress_key=progress_key) + except StopIteration: + logger.error('Mismatch between data file and progress database') + raise ResumeError( + 'Mismatch between data file and progress database') + elif state == DATA_CONSUMED_TO_HERE: + try: + self._AdvanceTo(key_end + 1) + except StopIteration: + state = None + + if self.progress_generator is None or state == DATA_CONSUMED_TO_HERE: + while not exhausted: + key_start = self.line_number + key_end = self.line_number + self.batch_size - 1 + try: + self._ReadRows(key_start, key_end) + except StopIteration: + exhausted = True + key_end = self.line_number - 1 + if key_start <= key_end: + yield self._MakeItem(key_start, key_end, self.read_rows) + + +class CSVGenerator(object): + """Reads a CSV file and generates data records.""" + + def __init__(self, + csv_filename, + openfile=open, + create_csv_reader=csv.reader): + """Initializes a CSV generator. + + Args: + csv_filename: File on disk containing CSV data. + openfile: Used for dependency injection of 'open'. + create_csv_reader: Used for dependency injection of 'csv.reader'. + """ + self.csv_filename = csv_filename + self.openfile = openfile + self.create_csv_reader = create_csv_reader + + def Records(self): + """Reads the CSV data file and generates row records. + + Yields: + Lists of strings + + Raises: + ResumeError: If the progress database and data file indicate a different + number of rows. + """ + csv_file = self.openfile(self.csv_filename, 'rb') + reader = self.create_csv_reader(csv_file, skipinitialspace=True) + try: + for record in reader: + yield record + except csv.Error, e: + if e.args and e.args[0].startswith('field larger than field limit'): + raise FieldSizeLimitError(csv.field_size_limit()) + else: + raise + + +class KeyRangeItemGenerator(object): + """Generates ranges of keys to download. + + Reads progress information from the progress database and creates + KeyRangeItem objects corresponding to incompletely downloaded parts of an + export. + """ + + def __init__(self, request_manager, kinds, progress_queue, progress_generator, + key_range_item_factory): + """Initialize the KeyRangeItemGenerator. + + Args: + request_manager: A RequestManager instance. + kinds: The kind of entities being transferred, or a list of kinds. + progress_queue: A queue used for tracking progress information. + progress_generator: A generator of prior progress information, or None + if there is no prior status. + key_range_item_factory: A factory to produce KeyRangeItems. + """ + self.request_manager = request_manager + if isinstance(kinds, basestring): + self.kinds = [kinds] + else: + self.kinds = kinds + self.row_count = 0 + self.xfer_count = 0 + self.progress_queue = progress_queue + self.progress_generator = progress_generator + self.key_range_item_factory = key_range_item_factory + + def Batches(self): + """Iterate through saved progress information. + + Yields: + KeyRangeItem instances corresponding to undownloaded key ranges. + """ + if self.progress_generator is not None: + for progress_key, kind, state, key_start, key_end in ( + self.progress_generator): + if state is not None and state != STATE_GOT and key_start is not None: + key_start = ParseKey(key_start) + key_end = ParseKey(key_end) + + key_range = KeyRange(key_start=key_start, + key_end=key_end) + + result = self.key_range_item_factory(self.request_manager, + self.progress_queue, + kind, + key_range, + progress_key=progress_key, + state=STATE_READ) + yield result + else: + for kind in self.kinds: + key_range = KeyRange() + yield self.key_range_item_factory(self.request_manager, + self.progress_queue, + kind, + key_range) + + +class DownloadResult(object): + """Holds the result of an entity download.""" + + def __init__(self, continued, direction, keys, entities): + self.continued = continued + self.direction = direction + self.keys = keys + self.entities = entities + self.count = len(keys) + assert self.count == len(entities) + assert direction in (key_range_module.KeyRange.ASC, + key_range_module.KeyRange.DESC) + if self.count > 0: + if direction == key_range_module.KeyRange.ASC: + self.key_start = keys[0] + self.key_end = keys[-1] + else: + self.key_start = keys[-1] + self.key_end = keys[0] + + def Entities(self): + """Returns the list of entities for this result in key order.""" + if self.direction == key_range_module.KeyRange.ASC: + return list(self.entities) + else: + result = list(self.entities) + result.reverse() + return result + + def __str__(self): + return 'continued = %s\n%s' % ( + str(self.continued), '\n'.join(str(self.entities))) + + +class _WorkItem(adaptive_thread_pool.WorkItem): + """Holds a description of a unit of upload or download work.""" + + def __init__(self, progress_queue, key_start, key_end, state_namer, + state=STATE_READ, progress_key=None): + """Initialize the _WorkItem instance. + + Args: + progress_queue: A queue used for tracking progress information. + key_start: The start key of the work item. + key_end: The end key of the work item. + state_namer: Function to describe work item states. + state: The initial state of the work item. + progress_key: If this WorkItem represents state from a prior run, + then this will be the key within the progress database. + """ + adaptive_thread_pool.WorkItem.__init__(self, + '[%s-%s]' % (key_start, key_end)) + self.progress_queue = progress_queue + self.state_namer = state_namer + self.state = state + self.progress_key = progress_key + self.progress_event = threading.Event() + self.key_start = key_start + self.key_end = key_end + self.error = None + self.traceback = None + self.kind = None + + def _TransferItem(self, thread_pool): + raise NotImplementedError() + + def SetError(self): + """Sets the error and traceback information for this thread. + + This must be called from an exception handler. + """ + if not self.error: + exc_info = sys.exc_info() + self.error = exc_info[1] + self.traceback = exc_info[2] + + def PerformWork(self, thread_pool): + """Perform the work of this work item and report the results. + + Args: + thread_pool: An AdaptiveThreadPool instance. + + Returns: + A tuple (status, instruction) of the work status and an instruction + for the ThreadGate. + """ + status = adaptive_thread_pool.WorkItem.FAILURE + instruction = adaptive_thread_pool.ThreadGate.DECREASE + + try: + self.MarkAsTransferring() + + try: + transfer_time = self._TransferItem(thread_pool) + if transfer_time is None: + status = adaptive_thread_pool.WorkItem.RETRY + instruction = adaptive_thread_pool.ThreadGate.HOLD + else: + logger.debug('[%s] %s Transferred %d entities in %0.1f seconds', + threading.currentThread().getName(), self, self.count, + transfer_time) + sys.stdout.write('.') + sys.stdout.flush() + status = adaptive_thread_pool.WorkItem.SUCCESS + if transfer_time <= MAXIMUM_INCREASE_DURATION: + instruction = adaptive_thread_pool.ThreadGate.INCREASE + elif transfer_time <= MAXIMUM_HOLD_DURATION: + instruction = adaptive_thread_pool.ThreadGate.HOLD + except (db.InternalError, db.NotSavedError, db.Timeout, + db.TransactionFailedError, + apiproxy_errors.OverQuotaError, + apiproxy_errors.DeadlineExceededError, + apiproxy_errors.ApplicationError), e: + status = adaptive_thread_pool.WorkItem.RETRY + logger.exception('Retrying on non-fatal datastore error: %s', e) + except urllib2.HTTPError, e: + http_status = e.code + if http_status >= 500 and http_status < 600: + status = adaptive_thread_pool.WorkItem.RETRY + logger.exception('Retrying on non-fatal HTTP error: %d %s', + http_status, e.msg) + else: + self.SetError() + status = adaptive_thread_pool.WorkItem.FAILURE + except urllib2.URLError, e: + if IsURLErrorFatal(e): + self.SetError() + status = adaptive_thread_pool.WorkItem.FAILURE + else: + status = adaptive_thread_pool.WorkItem.RETRY + logger.exception('Retrying on non-fatal URL error: %s', e.reason) + + finally: + if status == adaptive_thread_pool.WorkItem.SUCCESS: + self.MarkAsTransferred() + else: + self.MarkAsError() + + return (status, instruction) + + def _AssertInState(self, *states): + """Raises an Error if the state of this range is not in states.""" + if not self.state in states: + raise BadStateError('%s:%s not in %s' % + (str(self), + self.state_namer(self.state), + map(self.state_namer, states))) + + def _AssertProgressKey(self): + """Raises an Error if the progress key is None.""" + if self.progress_key is None: + raise BadStateError('%s: Progress key is missing' % str(self)) + + def MarkAsRead(self): + """Mark this _WorkItem as read, updating the progress database.""" + self._AssertInState(STATE_READ) + self._StateTransition(STATE_READ, blocking=True) + + def MarkAsTransferring(self): + """Mark this _WorkItem as transferring, updating the progress database.""" + self._AssertInState(STATE_READ, STATE_ERROR) + self._AssertProgressKey() + self._StateTransition(STATE_GETTING, blocking=True) + + def MarkAsTransferred(self): + """Mark this _WorkItem as transferred, updating the progress database.""" + raise NotImplementedError() + + def MarkAsError(self): + """Mark this _WorkItem as failed, updating the progress database.""" + self._AssertInState(STATE_GETTING) + self._AssertProgressKey() + self._StateTransition(STATE_ERROR, blocking=True) + + def _StateTransition(self, new_state, blocking=False): + """Transition the work item to a new state, storing progress information. + + Args: + new_state: The state to transition to. + blocking: Whether to block for the progress thread to acknowledge the + transition. + """ + assert not self.progress_event.isSet() + + self.state = new_state + + self.progress_queue.put(self) + + if blocking: + self.progress_event.wait() + + self.progress_event.clear() + + + +class UploadWorkItem(_WorkItem): + """Holds a unit of uploading work. + + A UploadWorkItem represents a number of entities that need to be uploaded to + Google App Engine. These entities are encoded in the "content" field of + the UploadWorkItem, and will be POST'd as-is to the server. + + The entities are identified by a range of numeric keys, inclusively. In + the case of a resumption of an upload, or a replay to correct errors, + these keys must be able to identify the same set of entities. + + Note that keys specify a range. The entities do not have to sequentially + fill the entire range, they must simply bound a range of valid keys. + """ + + def __init__(self, request_manager, progress_queue, rows, key_start, key_end, + progress_key=None): + """Initialize the UploadWorkItem instance. + + Args: + request_manager: A RequestManager instance. + progress_queue: A queue used for tracking progress information. + rows: A list of pairs of a line number and a list of column values. + key_start: The (numeric) starting key, inclusive. + key_end: The (numeric) ending key, inclusive. + progress_key: If this UploadWorkItem represents state from a prior run, + then this will be the key within the progress database. + """ + _WorkItem.__init__(self, progress_queue, key_start, key_end, + ImportStateName, state=STATE_READ, + progress_key=progress_key) + + assert isinstance(key_start, (int, long)) + assert isinstance(key_end, (int, long)) + assert key_start <= key_end + + self.request_manager = request_manager + self.rows = rows + self.content = None + self.count = len(rows) + + def __str__(self): + return '[%s-%s]' % (self.key_start, self.key_end) + + def _TransferItem(self, thread_pool, get_time=time.time): + """Transfers the entities associated with an item. + + Args: + thread_pool: An AdaptiveThreadPool instance. + get_time: Used for dependency injection. + """ + t = get_time() + if not self.content: + self.content = self.request_manager.EncodeContent(self.rows) + try: + self.request_manager.PostEntities(self.content) + except: + raise + return get_time() - t + + def MarkAsTransferred(self): + """Mark this UploadWorkItem as sucessfully-sent to the server.""" + + self._AssertInState(STATE_SENDING) + self._AssertProgressKey() + + self._StateTransition(STATE_SENT, blocking=False) + + +def GetImplementationClass(kind_or_class_key): + """Returns the implementation class for a given kind or class key. + + Args: + kind_or_class_key: A kind string or a tuple of kind strings. + + Return: + A db.Model subclass for the given kind or class key. + """ + if isinstance(kind_or_class_key, tuple): + try: + implementation_class = polymodel._class_map[kind_or_class_key] + except KeyError: + raise db.KindError('No implementation for class \'%s\'' % + kind_or_class_key) + else: + implementation_class = db.class_for_kind(kind_or_class_key) + return implementation_class + + +def KeyLEQ(key1, key2): + """Compare two keys for less-than-or-equal-to. + + All keys with numeric ids come before all keys with names. None represents + an unbounded end-point so it is both greater and less than any other key. + + Args: + key1: An int or datastore.Key instance. + key2: An int or datastore.Key instance. + + Returns: + True if key1 <= key2 + """ + if key1 is None or key2 is None: + return True + return key1 <= key2 + + +class KeyRangeItem(_WorkItem): + """Represents an item of work that scans over a key range. + + A KeyRangeItem object represents holds a KeyRange + and has an associated state: STATE_READ, STATE_GETTING, STATE_GOT, + and STATE_ERROR. + + - STATE_READ indicates the range ready to be downloaded by a worker thread. + - STATE_GETTING indicates the range is currently being downloaded. + - STATE_GOT indicates that the range was successfully downloaded + - STATE_ERROR indicates that an error occurred during the last download + attempt + + KeyRangeItems not in the STATE_GOT state are stored in the progress database. + When a piece of KeyRangeItem work is downloaded, the download may cover only + a portion of the range. In this case, the old KeyRangeItem is removed from + the progress database and ranges covering the undownloaded range are + generated and stored as STATE_READ in the export progress database. + """ + + def __init__(self, + request_manager, + progress_queue, + kind, + key_range, + progress_key=None, + state=STATE_READ, + first=False): + """Initialize a KeyRangeItem object. + + Args: + request_manager: A RequestManager instance. + progress_queue: A queue used for tracking progress information. + kind: The kind of entities for this range. + key_range: A KeyRange instance for this work item. + progress_key: The key for this range within the progress database. + state: The initial state of this range. + first: boolean, default False, whether this is the first WorkItem + of its kind. + """ + _WorkItem.__init__(self, progress_queue, key_range.key_start, + key_range.key_end, ExportStateName, state=state, + progress_key=progress_key) + assert KeyLEQ(key_range.key_start, key_range.key_end), ( + '%s not less than %s' % + (repr(key_range.key_start), repr(key_range.key_end))) + self.request_manager = request_manager + self.kind = kind + self.key_range = key_range + self.download_result = None + self.count = 0 + self.key_start = key_range.key_start + self.key_end = key_range.key_end + self.first = first + + def __str__(self): + return '%s-%s' % (self.kind, self.key_range) + + def __repr__(self): + return self.__str__() + + def MarkAsTransferred(self): + """Mark this KeyRangeItem as transferred, updating the progress database.""" + pass + + def Process(self, download_result, thread_pool, batch_size, + new_state=STATE_GOT): + """Mark this KeyRangeItem as success, updating the progress database. + + Process will split this KeyRangeItem based on the content of + download_result and adds the unfinished ranges to the work queue. + + Args: + download_result: A DownloadResult instance. + thread_pool: An AdaptiveThreadPool instance. + batch_size: The number of entities to transfer per request. + new_state: The state to transition the completed range to. + """ + self._AssertInState(STATE_GETTING) + self._AssertProgressKey() + + self.download_result = download_result + self.count = len(download_result.keys) + if download_result.continued: + self._FinishedRange()._StateTransition(new_state, blocking=True) + self._AddUnfinishedRanges(thread_pool, batch_size) + else: + self._StateTransition(new_state, blocking=True) + + def _FinishedRange(self): + """Returns the range completed by the download_result. + + Returns: + A KeyRangeItem representing a completed range. + """ + assert self.download_result is not None + + if self.key_range.direction == key_range_module.KeyRange.ASC: + key_start = self.key_range.key_start + if self.download_result.continued: + key_end = self.download_result.key_end + else: + key_end = self.key_range.key_end + else: + key_end = self.key_range.key_end + if self.download_result.continued: + key_start = self.download_result.key_start + else: + key_start = self.key_range.key_start + + key_range = KeyRange(key_start=key_start, + key_end=key_end, + direction=self.key_range.direction) + + result = self.__class__(self.request_manager, + self.progress_queue, + self.kind, + key_range, + progress_key=self.progress_key, + state=self.state) + + result.download_result = self.download_result + result.count = self.count + return result + + def _SplitAndAddRanges(self, thread_pool, batch_size): + """Split the key range [key_start, key_end] into a list of ranges.""" + if self.download_result.direction == key_range_module.KeyRange.ASC: + key_range = KeyRange( + key_start=self.download_result.key_end, + key_end=self.key_range.key_end, + include_start=False) + else: + key_range = KeyRange( + key_start=self.key_range.key_start, + key_end=self.download_result.key_start, + include_end=False) + + if thread_pool.QueuedItemCount() > 2 * thread_pool.num_threads(): + ranges = [key_range] + else: + ranges = key_range.split_range(batch_size=batch_size) + + for key_range in ranges: + key_range_item = self.__class__(self.request_manager, + self.progress_queue, + self.kind, + key_range) + key_range_item.MarkAsRead() + thread_pool.SubmitItem(key_range_item, block=True) + + def _AddUnfinishedRanges(self, thread_pool, batch_size): + """Adds incomplete KeyRanges to the thread_pool. + + Args: + thread_pool: An AdaptiveThreadPool instance. + batch_size: The number of entities to transfer per request. + + Returns: + A list of KeyRanges representing incomplete datastore key ranges. + + Raises: + KeyRangeError: if this key range has already been completely transferred. + """ + assert self.download_result is not None + if self.download_result.continued: + self._SplitAndAddRanges(thread_pool, batch_size) + else: + raise KeyRangeError('No unfinished part of key range.') + + +class DownloadItem(KeyRangeItem): + """A KeyRangeItem for downloading key ranges.""" + + def _TransferItem(self, thread_pool, get_time=time.time): + """Transfers the entities associated with an item.""" + t = get_time() + download_result = self.request_manager.GetEntities( + self, retry_parallel=self.first) + transfer_time = get_time() - t + self.Process(download_result, thread_pool, + self.request_manager.batch_size) + return transfer_time + + +class MapperItem(KeyRangeItem): + """A KeyRangeItem for mapping over key ranges.""" + + def _TransferItem(self, thread_pool, get_time=time.time): + t = get_time() + mapper = self.request_manager.GetMapper(self.kind) + + download_result = self.request_manager.GetEntities( + self, keys_only=mapper.map_over_keys_only(), retry_parallel=self.first) + transfer_time = get_time() - t + try: + mapper.batch_apply(download_result.Entities()) + except MapperRetry: + return None + self.Process(download_result, thread_pool, + self.request_manager.batch_size) + return transfer_time + + +class RequestManager(object): + """A class which wraps a connection to the server.""" + + def __init__(self, + app_id, + host_port, + url_path, + kind, + throttle, + batch_size, + secure, + email, + passin, + dry_run=False): + """Initialize a RequestManager object. + + Args: + app_id: String containing the application id for requests. + host_port: String containing the "host:port" pair; the port is optional. + url_path: partial URL (path) to post entity data to. + kind: Kind of the Entity records being posted. + throttle: A Throttle instance. + batch_size: The number of entities to transfer per request. + secure: Use SSL when communicating with server. + email: If not none, the username to log in with. + passin: If True, the password will be read from standard in. + """ + self.app_id = app_id + self.host_port = host_port + self.host = host_port.split(':')[0] + if url_path and url_path[0] != '/': + url_path = '/' + url_path + self.url_path = url_path + self.kind = kind + self.throttle = throttle + self.batch_size = batch_size + self.secure = secure + self.authenticated = False + self.auth_called = False + self.parallel_download = True + self.email = email + self.passin = passin + self.mapper = None + self.dry_run = dry_run + + if self.dry_run: + logger.info('Running in dry run mode, skipping remote_api setup') + return + + logger.debug('Configuring remote_api. url_path = %s, ' + 'servername = %s' % (url_path, host_port)) + + throttled_rpc_server_factory = ( + remote_api_throttle.ThrottledHttpRpcServerFactory(self.throttle)) + + remote_api_stub.ConfigureRemoteDatastore( + app_id, + url_path, + self.AuthFunction, + servername=host_port, + rpc_server_factory=throttled_rpc_server_factory, + secure=self.secure) + remote_api_throttle.ThrottleRemoteDatastore(self.throttle) + logger.debug('Bulkloader using app_id: %s', os.environ['APPLICATION_ID']) + + def Authenticate(self): + """Invoke authentication if necessary.""" + logger.info('Connecting to %s%s', self.host_port, self.url_path) + if self.dry_run: + self.authenticated = True + return + + remote_api_stub.MaybeInvokeAuthentication() + self.authenticated = True + + def AuthFunction(self, + raw_input_fn=raw_input, + password_input_fn=getpass.getpass): + """Prompts the user for a username and password. + + Caches the results the first time it is called and returns the + same result every subsequent time. + + Args: + raw_input_fn: Used for dependency injection. + password_input_fn: Used for dependency injection. + + Returns: + A pair of the username and password. + """ + if self.email: + email = self.email + else: + print 'Please enter login credentials for %s' % ( + self.host) + email = raw_input_fn('Email: ') + + if email: + password_prompt = 'Password for %s: ' % email + if self.passin: + password = raw_input_fn(password_prompt) + else: + password = password_input_fn(password_prompt) + else: + password = None + + self.auth_called = True + return (email, password) + + def IncrementId(self, ancestor_path, kind, high_id): + """Increment the unique id counter associated with ancestor_path and kind. + + Args: + ancestor_path: A list encoding the path of a key. + kind: The string name of a kind. + high_id: The int value to which to increment the unique id counter. + """ + model_key = datastore.Key.from_path(*(ancestor_path + [kind, 1])) + start, end = datastore.AllocateIds(model_key, 1) + if end < high_id: + start, end = datastore.AllocateIds(model_key, high_id - end) + assert end >= high_id + + def GetSchemaKinds(self): + """Returns the list of kinds for this app.""" + global_stat = stats.GlobalStat.all().get() + if not global_stat: + raise KindStatError() + timestamp = global_stat.timestamp + kind_stat = stats.KindStat.all().filter( + "timestamp =", timestamp).fetch(1000) + kind_list = [stat.kind_name for stat in kind_stat + if stat.kind_name and not stat.kind_name.startswith('__')] + kind_set = set(kind_list) + return list(kind_set) + + def EncodeContent(self, rows, loader=None): + """Encodes row data to the wire format. + + Args: + rows: A list of pairs of a line number and a list of column values. + loader: Used for dependency injection. + + Returns: + A list of datastore.Entity instances. + + Raises: + ConfigurationError: if no loader is defined for self.kind + """ + if not loader: + try: + loader = Loader.RegisteredLoader(self.kind) + except KeyError: + logger.error('No Loader defined for kind %s.' % self.kind) + raise ConfigurationError('No Loader defined for kind %s.' % self.kind) + entities = [] + for line_number, values in rows: + key = loader.generate_key(line_number, values) + if isinstance(key, datastore.Key): + parent = key.parent() + key = key.name() + else: + parent = None + entity = loader.create_entity(values, key_name=key, parent=parent) + + def ToEntity(entity): + if isinstance(entity, db.Model): + return entity._populate_entity() + else: + return entity + + if not entity: + continue + if isinstance(entity, list): + entities.extend(map(ToEntity, entity)) + elif entity: + entities.append(ToEntity(entity)) + + return entities + + def PostEntities(self, entities): + """Posts Entity records to a remote endpoint over HTTP. + + Args: + entities: A list of datastore entities. + """ + if self.dry_run: + return + datastore.Put(entities) + + def _QueryForPbs(self, query): + """Perform the given query and return a list of entity_pb's.""" + try: + query_pb = query._ToPb(limit=self.batch_size, count=self.batch_size) + result_pb = datastore_pb.QueryResult() + apiproxy_stub_map.MakeSyncCall('datastore_v3', 'RunQuery', query_pb, + result_pb) + if result_pb.result_size() == 0 and result_pb.more_results(): + next_pb = datastore_pb.NextRequest() + next_pb.set_count(self.batch_size) + next_pb.mutable_cursor().CopyFrom(result_pb.cursor()) + result_pb = datastore_pb.QueryResult() + apiproxy_stub_map.MakeSyncCall('datastore_v3', 'Next', next_pb, + result_pb) + + return result_pb.result_list() + except apiproxy_errors.ApplicationError, e: + raise datastore._ToDatastoreError(e) + + def GetEntities( + self, key_range_item, key_factory=datastore.Key, keys_only=False, + retry_parallel=False): + """Gets Entity records from a remote endpoint over HTTP. + + Args: + key_range_item: Range of keys to get. + key_factory: Used for dependency injection. + keys_only: bool, default False, only get keys values + retry_parallel: bool, default False, to try a parallel download despite + past parallel download failures. + Returns: + A DownloadResult instance. + + Raises: + ConfigurationError: if no Exporter is defined for key_range_item.kind + """ + keys = [] + entities = [] + kind = key_range_item.kind + if retry_parallel: + self.parallel_download = True + + if self.parallel_download: + query = key_range_item.key_range.make_directed_datastore_query( + kind, keys_only=keys_only) + try: + results = self._QueryForPbs(query) + except datastore_errors.NeedIndexError: + logger.info('%s: No descending index on __key__, ' + 'performing serial download', kind) + self.parallel_download = False + + if not self.parallel_download: + key_range_item.key_range.direction = key_range_module.KeyRange.ASC + query = key_range_item.key_range.make_ascending_datastore_query( + kind, keys_only=keys_only) + results = self._QueryForPbs(query) + + size = len(results) + + for entity in results: + key = key_factory() + key._Key__reference = entity.key() + entities.append(entity) + keys.append(key) + + continued = (size == self.batch_size) + key_range_item.count = size + + return DownloadResult(continued, key_range_item.key_range.direction, + keys, entities) + + def GetMapper(self, kind): + """Returns a mapper for the registered kind. + + Returns: + A Mapper instance. + + Raises: + ConfigurationError: if no Mapper is defined for kind + """ + if not self.mapper: + try: + self.mapper = Mapper.RegisteredMapper(kind) + except KeyError: + logger.error('No Mapper defined for kind %s.' % kind) + raise ConfigurationError('No Mapper defined for kind %s.' % kind) + return self.mapper + + +def InterruptibleSleep(sleep_time): + """Puts thread to sleep, checking this threads exit_flag twice a second. + + Args: + sleep_time: Time to sleep. + """ + slept = 0.0 + epsilon = .0001 + thread = threading.currentThread() + while slept < sleep_time - epsilon: + remaining = sleep_time - slept + this_sleep_time = min(remaining, 0.5) + time.sleep(this_sleep_time) + slept += this_sleep_time + if thread.exit_flag: + return + + +class _ThreadBase(threading.Thread): + """Provide some basic features for the threads used in the uploader. + + This abstract base class is used to provide some common features: + + * Flag to ask thread to exit as soon as possible. + * Record exit/error status for the primary thread to pick up. + * Capture exceptions and record them for pickup. + * Some basic logging of thread start/stop. + * All threads are "daemon" threads. + * Friendly names for presenting to users. + + Concrete sub-classes must implement PerformWork(). + + Either self.NAME should be set or GetFriendlyName() be overridden to + return a human-friendly name for this thread. + + The run() method starts the thread and prints start/exit messages. + + self.exit_flag is intended to signal that this thread should exit + when it gets the chance. PerformWork() should check self.exit_flag + whenever it has the opportunity to exit gracefully. + """ + + def __init__(self): + threading.Thread.__init__(self) + + self.setDaemon(True) + + self.exit_flag = False + self.error = None + self.traceback = None + + def run(self): + """Perform the work of the thread.""" + logger.debug('[%s] %s: started', self.getName(), self.__class__.__name__) + + try: + self.PerformWork() + except: + self.SetError() + logger.exception('[%s] %s:', self.getName(), self.__class__.__name__) + + logger.debug('[%s] %s: exiting', self.getName(), self.__class__.__name__) + + def SetError(self): + """Sets the error and traceback information for this thread. + + This must be called from an exception handler. + """ + if not self.error: + exc_info = sys.exc_info() + self.error = exc_info[1] + self.traceback = exc_info[2] + + def PerformWork(self): + """Perform the thread-specific work.""" + raise NotImplementedError() + + def CheckError(self): + """If an error is present, then log it.""" + if self.error: + logger.error('Error in %s: %s', self.GetFriendlyName(), self.error) + if self.traceback: + logger.debug(''.join(traceback.format_exception(self.error.__class__, + self.error, + self.traceback))) + + def GetFriendlyName(self): + """Returns a human-friendly description of the thread.""" + if hasattr(self, 'NAME'): + return self.NAME + return 'unknown thread' + + +non_fatal_error_codes = set([errno.EAGAIN, + errno.ENETUNREACH, + errno.ENETRESET, + errno.ECONNRESET, + errno.ETIMEDOUT, + errno.EHOSTUNREACH]) + + +def IsURLErrorFatal(error): + """Returns False if the given URLError may be from a transient failure. + + Args: + error: A urllib2.URLError instance. + """ + assert isinstance(error, urllib2.URLError) + if not hasattr(error, 'reason'): + return True + if not isinstance(error.reason[0], int): + return True + return error.reason[0] not in non_fatal_error_codes + + +class DataSourceThread(_ThreadBase): + """A thread which reads WorkItems and pushes them into queue. + + This thread will read/consume WorkItems from a generator (produced by + the generator factory). These WorkItems will then be pushed into the + thread_pool. Note that reading will block if/when the thread_pool becomes + full. Information on content consumed from the generator will be pushed + into the progress_queue. + """ + + NAME = 'data source thread' + + def __init__(self, + request_manager, + kinds, + thread_pool, + progress_queue, + workitem_generator_factory, + progress_generator_factory): + """Initialize the DataSourceThread instance. + + Args: + request_manager: A RequestManager instance. + kinds: The kinds of entities being transferred. + thread_pool: An AdaptiveThreadPool instance. + progress_queue: A queue used for tracking progress information. + workitem_generator_factory: A factory that creates a WorkItem generator + progress_generator_factory: A factory that creates a generator which + produces prior progress status, or None if there is no prior status + to use. + """ + _ThreadBase.__init__(self) + + self.request_manager = request_manager + self.kinds = kinds + self.thread_pool = thread_pool + self.progress_queue = progress_queue + self.workitem_generator_factory = workitem_generator_factory + self.progress_generator_factory = progress_generator_factory + self.entity_count = 0 + + def PerformWork(self): + """Performs the work of a DataSourceThread.""" + if self.progress_generator_factory: + progress_gen = self.progress_generator_factory() + else: + progress_gen = None + + content_gen = self.workitem_generator_factory(self.request_manager, + self.progress_queue, + progress_gen, + self.kinds) + + self.xfer_count = 0 + self.read_count = 0 + self.read_all = False + + for item in content_gen.Batches(): + item.MarkAsRead() + + while not self.exit_flag: + try: + self.thread_pool.SubmitItem(item, block=True, timeout=1.0) + self.entity_count += item.count + break + except Queue.Full: + pass + + if self.exit_flag: + break + + if not self.exit_flag: + self.read_all = True + self.read_count = content_gen.row_count + self.xfer_count = content_gen.xfer_count + + + +def _RunningInThread(thread): + """Return True if we are running within the specified thread.""" + return threading.currentThread().getName() == thread.getName() + + +class _Database(object): + """Base class for database connections in this module. + + The table is created by a primary thread (the python main thread) + but all future lookups and updates are performed by a secondary + thread. + """ + + SIGNATURE_TABLE_NAME = 'bulkloader_database_signature' + + def __init__(self, + db_filename, + create_table, + signature, + index=None, + commit_periodicity=100): + """Initialize the _Database instance. + + Args: + db_filename: The sqlite3 file to use for the database. + create_table: A string containing the SQL table creation command. + signature: A string identifying the important invocation options, + used to make sure we are not using an old database. + index: An optional string to create an index for the database. + commit_periodicity: Number of operations between database commits. + """ + self.db_filename = db_filename + + logger.info('Opening database: %s', db_filename) + self.primary_conn = sqlite3.connect(db_filename, isolation_level=None) + self.primary_thread = threading.currentThread() + + self.secondary_conn = None + self.secondary_thread = None + + self.operation_count = 0 + self.commit_periodicity = commit_periodicity + + try: + self.primary_conn.execute(create_table) + except sqlite3.OperationalError, e: + if 'already exists' not in e.message: + raise + + if index: + try: + self.primary_conn.execute(index) + except sqlite3.OperationalError, e: + if 'already exists' not in e.message: + raise + + self.existing_table = False + signature_cursor = self.primary_conn.cursor() + create_signature = """ + create table %s ( + value TEXT not null) + """ % _Database.SIGNATURE_TABLE_NAME + try: + self.primary_conn.execute(create_signature) + self.primary_conn.cursor().execute( + 'insert into %s (value) values (?)' % _Database.SIGNATURE_TABLE_NAME, + (signature,)) + except sqlite3.OperationalError, e: + if 'already exists' not in e.message: + logger.exception('Exception creating table:') + raise + else: + self.existing_table = True + signature_cursor.execute( + 'select * from %s' % _Database.SIGNATURE_TABLE_NAME) + (result,) = signature_cursor.fetchone() + if result and result != signature: + logger.error('Database signature mismatch:\n\n' + 'Found:\n' + '%s\n\n' + 'Expecting:\n' + '%s\n', + result, signature) + raise ResumeError('Database signature mismatch: %s != %s' % ( + signature, result)) + + def ThreadComplete(self): + """Finalize any operations the secondary thread has performed. + + The database aggregates lots of operations into a single commit, and + this method is used to commit any pending operations as the thread + is about to shut down. + """ + if self.secondary_conn: + self._MaybeCommit(force_commit=True) + + def _MaybeCommit(self, force_commit=False): + """Periodically commit changes into the SQLite database. + + Committing every operation is quite expensive, and slows down the + operation of the script. Thus, we only commit after every N operations, + as determined by the self.commit_periodicity value. Optionally, the + caller can force a commit. + + Args: + force_commit: Pass True in order for a commit to occur regardless + of the current operation count. + """ + self.operation_count += 1 + if force_commit or (self.operation_count % self.commit_periodicity) == 0: + self.secondary_conn.commit() + + def _OpenSecondaryConnection(self): + """Possibly open a database connection for the secondary thread. + + If the connection is not open (for the calling thread, which is assumed + to be the unique secondary thread), then open it. We also open a couple + cursors for later use (and reuse). + """ + if self.secondary_conn: + return + + assert not _RunningInThread(self.primary_thread) + + self.secondary_thread = threading.currentThread() + + self.secondary_conn = sqlite3.connect(self.db_filename) + + self.insert_cursor = self.secondary_conn.cursor() + self.update_cursor = self.secondary_conn.cursor() + + +zero_matcher = re.compile(r'\x00') + +zero_one_matcher = re.compile(r'\x00\x01') + + +def KeyStr(key): + """Returns a string to represent a key, preserving ordering. + + Unlike datastore.Key.__str__(), we have the property: + + key1 < key2 ==> KeyStr(key1) < KeyStr(key2) + + The key string is constructed from the key path as follows: + (1) Strings are prepended with ':' and numeric id's are padded to + 20 digits. + (2) Any null characters (u'\0') present are replaced with u'\0\1' + (3) The sequence u'\0\0' is used to separate each component of the path. + + (1) assures that names and ids compare properly, while (2) and (3) enforce + the part-by-part comparison of pieces of the path. + + Args: + key: A datastore.Key instance. + + Returns: + A string representation of the key, which preserves ordering. + """ + assert isinstance(key, datastore.Key) + path = key.to_path() + + out_path = [] + for part in path: + if isinstance(part, (int, long)): + part = '%020d' % part + else: + part = ':%s' % part + + out_path.append(zero_matcher.sub(u'\0\1', part)) + + out_str = u'\0\0'.join(out_path) + + return out_str + + +def StrKey(key_str): + """The inverse of the KeyStr function. + + Args: + key_str: A string in the range of KeyStr. + + Returns: + A datastore.Key instance k, such that KeyStr(k) == key_str. + """ + parts = key_str.split(u'\0\0') + for i in xrange(len(parts)): + if parts[i][0] == ':': + part = parts[i][1:] + part = zero_one_matcher.sub(u'\0', part) + parts[i] = part + else: + parts[i] = int(parts[i]) + return datastore.Key.from_path(*parts) + + +class ResultDatabase(_Database): + """Persistently record all the entities downloaded during an export. + + The entities are held in the database by their unique datastore key + in order to avoid duplication if an export is restarted. + """ + + def __init__(self, db_filename, signature, commit_periodicity=1, + exporter=None): + """Initialize a ResultDatabase object. + + Args: + db_filename: The name of the SQLite database to use. + signature: A string identifying the important invocation options, + used to make sure we are not using an old database. + commit_periodicity: How many operations to perform between commits. + exporter: Exporter instance; if exporter.calculate_sort_key_from_entity + is true then exporter.sort_key_from_entity(entity) will be called. + """ + self.complete = False + create_table = ('create table result (\n' + 'id BLOB primary key,\n' + 'value BLOB not null,\n' + 'sort_key BLOB)') + + _Database.__init__(self, + db_filename, + create_table, + signature, + commit_periodicity=commit_periodicity) + if self.existing_table: + cursor = self.primary_conn.cursor() + cursor.execute('select count(*) from result') + self.existing_count = int(cursor.fetchone()[0]) + else: + self.existing_count = 0 + self.count = self.existing_count + if exporter and getattr(exporter, 'calculate_sort_key_from_entity', False): + self.sort_key_from_entity = exporter.sort_key_from_entity + else: + self.sort_key_from_entity = None + + def _StoreEntity(self, entity_id, entity): + """Store an entity in the result database. + + Args: + entity_id: A datastore.Key for the entity. + entity: The entity to store. + + Returns: + True if this entities is not already present in the result database. + """ + assert _RunningInThread(self.secondary_thread) + assert isinstance(entity_id, datastore.Key), ( + 'expected a datastore.Key, got a %s' % entity_id.__class__.__name__) + + key_str = buffer(KeyStr(entity_id).encode('utf-8')) + self.insert_cursor.execute( + 'select count(*) from result where id = ?', (key_str,)) + + already_present = self.insert_cursor.fetchone()[0] + result = True + if already_present: + result = False + self.insert_cursor.execute('delete from result where id = ?', + (key_str,)) + else: + self.count += 1 + if self.sort_key_from_entity: + sort_key = self.sort_key_from_entity(datastore.Entity._FromPb(entity)) + else: + sort_key = '' + value = entity.Encode() + self.insert_cursor.execute( + 'insert into result (id, value, sort_key) values (?, ?, ?)', + (key_str, buffer(value), sort_key)) + return result + + def StoreEntities(self, keys, entities): + """Store a group of entities in the result database. + + Args: + keys: A list of entity keys. + entities: A list of entities. + + Returns: + The number of new entities stored in the result database. + """ + self._OpenSecondaryConnection() + t = time.time() + count = 0 + for entity_id, entity in zip(keys, + entities): + if self._StoreEntity(entity_id, entity): + count += 1 + logger.debug('%s insert: delta=%.3f', + self.db_filename, + time.time() - t) + logger.debug('Entities transferred total: %s', self.count) + self._MaybeCommit() + return count + + def ResultsComplete(self): + """Marks the result database as containing complete results.""" + self.complete = True + + def AllEntities(self): + """Yields all pairs of (id, value) from the result table.""" + conn = sqlite3.connect(self.db_filename, isolation_level=None) + cursor = conn.cursor() + + cursor.execute( + 'select id, value from result order by sort_key, id') + + for unused_entity_id, entity in cursor: + entity_proto = entity_pb.EntityProto(contents=entity) + yield datastore.Entity._FromPb(entity_proto) + + +class _ProgressDatabase(_Database): + """Persistently record all progress information during an upload. + + This class wraps a very simple SQLite database which records each of + the relevant details from a chunk of work. If the loader is + resumed, then data is replayed out of the database. + """ + + def __init__(self, + db_filename, + sql_type, + py_type, + signature, + commit_periodicity=100): + """Initialize the ProgressDatabase instance. + + Args: + db_filename: The name of the SQLite database to use. + sql_type: A string of the SQL type to use for entity keys. + py_type: The python type of entity keys. + signature: A string identifying the important invocation options, + used to make sure we are not using an old database. + commit_periodicity: How many operations to perform between commits. + """ + self.prior_key_end = None + + create_table = ('create table progress (\n' + 'id integer primary key autoincrement,\n' + 'state integer not null,\n' + 'kind text not null,\n' + 'key_start %s,\n' + 'key_end %s)' + % (sql_type, sql_type)) + self.py_type = py_type + + index = 'create index i_state on progress (state)' + _Database.__init__(self, + db_filename, + create_table, + signature, + index=index, + commit_periodicity=commit_periodicity) + + def UseProgressData(self): + """Returns True if the database has progress information. + + Note there are two basic cases for progress information: + 1) All saved records indicate a successful upload. In this case, we + need to skip everything transmitted so far and then send the rest. + 2) Some records for incomplete transfer are present. These need to be + sent again, and then we resume sending after all the successful + data. + + Returns: + True: if the database has progress information. + + Raises: + ResumeError: if there is an error retrieving rows from the database. + """ + assert _RunningInThread(self.primary_thread) + + cursor = self.primary_conn.cursor() + cursor.execute('select count(*) from progress') + row = cursor.fetchone() + if row is None: + raise ResumeError('Cannot retrieve progress information from database.') + + return row[0] != 0 + + def StoreKeys(self, kind, key_start, key_end): + """Record a new progress record, returning a key for later updates. + + The specified progress information will be persisted into the database. + A unique key will be returned that identifies this progress state. The + key is later used to (quickly) update this record. + + For the progress resumption to proceed properly, calls to StoreKeys + MUST specify monotonically increasing key ranges. This will result in + a database whereby the ID, KEY_START, and KEY_END rows are all + increasing (rather than having ranges out of order). + + NOTE: the above precondition is NOT tested by this method (since it + would imply an additional table read or two on each invocation). + + Args: + kind: The kind for the WorkItem + key_start: The starting key of the WorkItem (inclusive) + key_end: The end key of the WorkItem (inclusive) + + Returns: + A string to later be used as a unique key to update this state. + """ + self._OpenSecondaryConnection() + + assert _RunningInThread(self.secondary_thread) + assert (not key_start) or isinstance(key_start, self.py_type), ( + '%s is a %s, %s expected %s' % (key_start, + key_start.__class__, + self.__class__.__name__, + self.py_type)) + assert (not key_end) or isinstance(key_end, self.py_type), ( + '%s is a %s, %s expected %s' % (key_end, + key_end.__class__, + self.__class__.__name__, + self.py_type)) + assert KeyLEQ(key_start, key_end), '%s not less than %s' % ( + repr(key_start), repr(key_end)) + + self.insert_cursor.execute( + 'insert into progress (state, kind, key_start, key_end)' + ' values (?, ?, ?, ?)', + (STATE_READ, unicode(kind), unicode(key_start), unicode(key_end))) + + progress_key = self.insert_cursor.lastrowid + + self._MaybeCommit() + + return progress_key + + def UpdateState(self, key, new_state): + """Update a specified progress record with new information. + + Args: + key: The key for this progress record, returned from StoreKeys + new_state: The new state to associate with this progress record. + """ + self._OpenSecondaryConnection() + + assert _RunningInThread(self.secondary_thread) + assert isinstance(new_state, int) + + self.update_cursor.execute('update progress set state=? where id=?', + (new_state, key)) + + self._MaybeCommit() + + def DeleteKey(self, progress_key): + """Delete the entities with the given key from the result database.""" + self._OpenSecondaryConnection() + + assert _RunningInThread(self.secondary_thread) + + t = time.time() + self.insert_cursor.execute( + 'delete from progress where rowid = ?', (progress_key,)) + + logger.debug('delete: delta=%.3f', time.time() - t) + + self._MaybeCommit() + + def GetProgressStatusGenerator(self): + """Get a generator which yields progress information. + + The returned generator will yield a series of 5-tuples that specify + progress information about a prior run of the uploader. The 5-tuples + have the following values: + + progress_key: The unique key to later update this record with new + progress information. + state: The last state saved for this progress record. + kind: The datastore kind of the items for uploading. + key_start: The starting key of the items for uploading (inclusive). + key_end: The ending key of the items for uploading (inclusive). + + After all incompletely-transferred records are provided, then one + more 5-tuple will be generated: + + None + DATA_CONSUMED_TO_HERE: A unique string value indicating this record + is being provided. + None + None + key_end: An integer value specifying the last data source key that + was handled by the previous run of the uploader. + + The caller should begin uploading records which occur after key_end. + + Yields: + Four-tuples of (progress_key, state, key_start, key_end) + """ + conn = sqlite3.connect(self.db_filename, isolation_level=None) + cursor = conn.cursor() + + cursor.execute('select max(key_end) from progress') + + result = cursor.fetchone() + if result is not None: + key_end = result[0] + else: + logger.debug('No rows in progress database.') + return + + self.prior_key_end = key_end + + cursor.execute( + 'select id, state, kind, key_start, key_end from progress' + ' where state != ?' + ' order by id', + (STATE_SENT,)) + + rows = cursor.fetchall() + + for row in rows: + if row is None: + break + progress_key, kind, state, key_start, key_end = row + + yield progress_key, kind, state, key_start, key_end + + yield None, DATA_CONSUMED_TO_HERE, None, None, key_end + + +def ProgressDatabase(db_filename, signature): + """Returns a database to store upload progress information.""" + return _ProgressDatabase(db_filename, 'INTEGER', int, signature) + + +class ExportProgressDatabase(_ProgressDatabase): + """A database to store download progress information.""" + + def __init__(self, db_filename, signature): + """Initialize an ExportProgressDatabase.""" + _ProgressDatabase.__init__(self, + db_filename, + 'TEXT', + datastore.Key, + signature, + commit_periodicity=1) + + def UseProgressData(self): + """Check if the progress database contains progress data. + + Returns: + True: if the database contains progress data. + """ + return self.existing_table + + +class StubProgressDatabase(object): + """A stub implementation of ProgressDatabase which does nothing.""" + + def UseProgressData(self): + """Whether the stub database has progress information (it doesn't).""" + return False + + def StoreKeys(self, unused_kind, unused_key_start, unused_key_end): + """Pretend to store a key in the stub database.""" + return 'fake-key' + + def UpdateState(self, unused_key, unused_new_state): + """Pretend to update the state of a progress item.""" + pass + + def ThreadComplete(self): + """Finalize operations on the stub database (i.e. do nothing).""" + pass + + def DeleteKey(self, unused_key): + """Delete the operations with a given key (but, do nothing.)""" + pass + + +class _ProgressThreadBase(_ThreadBase): + """A thread which records progress information for the upload process. + + The progress information is stored into the provided progress database. + This class is not responsible for replaying a prior run's progress + information out of the database. Separate mechanisms must be used to + resume a prior upload attempt. + """ + + NAME = 'progress tracking thread' + + def __init__(self, progress_queue, progress_db): + """Initialize the ProgressTrackerThread instance. + + Args: + progress_queue: A Queue used for tracking progress information. + progress_db: The database for tracking progress information; should + be an instance of ProgressDatabase. + """ + _ThreadBase.__init__(self) + + self.progress_queue = progress_queue + self.db = progress_db + self.entities_transferred = 0 + + def EntitiesTransferred(self): + """Return the total number of unique entities transferred.""" + return self.entities_transferred + + def UpdateProgress(self, item): + """Updates the progress information for the given item. + + Args: + item: A work item whose new state will be recorded + """ + raise NotImplementedError() + + def WorkFinished(self): + """Performs final actions after the entity transfer is complete.""" + raise NotImplementedError() + + def PerformWork(self): + """Performs the work of a ProgressTrackerThread.""" + while not self.exit_flag: + try: + item = self.progress_queue.get(block=True, timeout=1.0) + except Queue.Empty: + continue + if item == _THREAD_SHOULD_EXIT: + break + + if item.state == STATE_READ and item.progress_key is None: + item.progress_key = self.db.StoreKeys(item.kind, + item.key_start, + item.key_end) + else: + assert item.progress_key is not None + self.UpdateProgress(item) + + item.progress_event.set() + + self.progress_queue.task_done() + + self.db.ThreadComplete() + + + +class ProgressTrackerThread(_ProgressThreadBase): + """A thread which records progress information for the upload process. + + The progress information is stored into the provided progress database. + This class is not responsible for replaying a prior run's progress + information out of the database. Separate mechanisms must be used to + resume a prior upload attempt. + """ + NAME = 'progress tracking thread' + + def __init__(self, progress_queue, progress_db): + """Initialize the ProgressTrackerThread instance. + + Args: + progress_queue: A Queue used for tracking progress information. + progress_db: The database for tracking progress information; should + be an instance of ProgressDatabase. + """ + _ProgressThreadBase.__init__(self, progress_queue, progress_db) + + def UpdateProgress(self, item): + """Update the state of the given WorkItem. + + Args: + item: A WorkItem instance. + """ + self.db.UpdateState(item.progress_key, item.state) + if item.state == STATE_SENT: + self.entities_transferred += item.count + + def WorkFinished(self): + """Performs final actions after the entity transfer is complete.""" + pass + + +class ExportProgressThread(_ProgressThreadBase): + """A thread to record progress information and write record data for exports. + + The progress information is stored into a provided progress database. + Exported results are stored in the result database and dumped to an output + file at the end of the download. + """ + + def __init__(self, exporter, progress_queue, progress_db, result_db): + """Initialize the ExportProgressThread instance. + + Args: + exporter: An Exporter instance for the download. + progress_queue: A Queue used for tracking progress information. + progress_db: The database for tracking progress information; should + be an instance of ProgressDatabase. + result_db: The database for holding exported entities; should be an + instance of ResultDatabase. + """ + _ProgressThreadBase.__init__(self, progress_queue, progress_db) + + self.exporter = exporter + self.existing_count = result_db.existing_count + self.result_db = result_db + + def EntitiesTransferred(self): + """Return the total number of unique entities transferred.""" + return self.result_db.count + + def WorkFinished(self): + """Write the contents of the result database.""" + self.exporter.output_entities(self.result_db.AllEntities()) + + def UpdateProgress(self, item): + """Update the state of the given KeyRangeItem. + + Args: + item: A KeyRange instance. + """ + if item.state == STATE_GOT: + count = self.result_db.StoreEntities(item.download_result.keys, + item.download_result.entities) + self.db.DeleteKey(item.progress_key) + self.entities_transferred += count + else: + self.db.UpdateState(item.progress_key, item.state) + + +class MapperProgressThread(_ProgressThreadBase): + """A thread to record progress information for maps over the datastore.""" + + def __init__(self, mapper, progress_queue, progress_db): + """Initialize the MapperProgressThread instance. + + Args: + mapper: A Mapper object for this map run. + progress_queue: A Queue used for tracking progress information. + progress_db: The database for tracking progress information; should + be an instance of ProgressDatabase. + """ + _ProgressThreadBase.__init__(self, progress_queue, progress_db) + + self.mapper = mapper + + def EntitiesTransferred(self): + """Return the total number of unique entities transferred.""" + return self.entities_transferred + + def WorkFinished(self): + """Perform actions after map is complete.""" + pass + + def UpdateProgress(self, item): + """Update the state of the given KeyRangeItem. + + Args: + item: A KeyRange instance. + """ + if item.state == STATE_GOT: + self.entities_transferred += item.count + self.db.DeleteKey(item.progress_key) + else: + self.db.UpdateState(item.progress_key, item.state) + + +def ParseKey(key_string): + """Turn a key stored in the database into a Key or None. + + Args: + key_string: The string representation of a Key. + + Returns: + A datastore.Key instance or None + """ + if not key_string: + return None + if key_string == 'None': + return None + return datastore.Key(encoded=key_string) + + +def Validate(value, typ): + """Checks that value is non-empty and of the right type. + + Args: + value: any value + typ: a type or tuple of types + + Raises: + ValueError: if value is None or empty. + TypeError: if it's not the given type. + """ + if not value: + raise ValueError('Value should not be empty; received %s.' % value) + elif not isinstance(value, typ): + raise TypeError('Expected a %s, but received %s (a %s).' % + (typ, value, value.__class__)) + + +def CheckFile(filename): + """Check that the given file exists and can be opened for reading. + + Args: + filename: The name of the file. + + Raises: + FileNotFoundError: if the given filename is not found + FileNotReadableError: if the given filename is not readable. + """ + if not os.path.exists(filename): + raise FileNotFoundError('%s: file not found' % filename) + elif not os.access(filename, os.R_OK): + raise FileNotReadableError('%s: file not readable' % filename) + + +class Loader(object): + """A base class for creating datastore entities from input data. + + To add a handler for bulk loading a new entity kind into your datastore, + write a subclass of this class that calls Loader.__init__ from your + class's __init__. + + If you need to run extra code to convert entities from the input + data, create new properties, or otherwise modify the entities before + they're inserted, override handle_entity. + + See the create_entity method for the creation of entities from the + (parsed) input data. + """ + + __loaders = {} + kind = None + __properties = None + + def __init__(self, kind, properties): + """Constructor. + + Populates this Loader's kind and properties map. + + Args: + kind: a string containing the entity kind that this loader handles + + properties: list of (name, converter) tuples. + + This is used to automatically convert the input columns into + properties. The converter should be a function that takes one + argument, a string value from the input file, and returns a + correctly typed property value that should be inserted. The + tuples in this list should match the columns in your input file, + in order. + + For example: + [('name', str), + ('id_number', int), + ('email', datastore_types.Email), + ('user', users.User), + ('birthdate', lambda x: datetime.datetime.fromtimestamp(float(x))), + ('description', datastore_types.Text), + ] + """ + Validate(kind, (basestring, tuple)) + self.kind = kind + self.__openfile = open + self.__create_csv_reader = csv.reader + + GetImplementationClass(kind) + + Validate(properties, list) + for name, fn in properties: + Validate(name, basestring) + assert callable(fn), ( + 'Conversion function %s for property %s is not callable.' % (fn, name)) + + self.__properties = properties + + @staticmethod + def RegisterLoader(loader): + """Register loader and the Loader instance for its kind. + + Args: + loader: A Loader instance. + """ + Loader.__loaders[loader.kind] = loader + + def get_high_ids(self): + """Returns dict {ancestor_path : {kind : id}} with high id values. + + The returned dictionary is used to increment the id counters + associated with each ancestor_path and kind to be at least id. + """ + return {} + + def alias_old_names(self): + """Aliases method names so that Loaders defined with old names work.""" + aliases = ( + ('CreateEntity', 'create_entity'), + ('HandleEntity', 'handle_entity'), + ('GenerateKey', 'generate_key'), + ) + for old_name, new_name in aliases: + setattr(Loader, old_name, getattr(Loader, new_name)) + if hasattr(self.__class__, old_name) and not ( + getattr(self.__class__, old_name).im_func == + getattr(Loader, new_name).im_func): + if hasattr(self.__class__, new_name) and not ( + getattr(self.__class__, new_name).im_func == + getattr(Loader, new_name).im_func): + raise NameClashError(old_name, new_name, self.__class__) + setattr(self, new_name, getattr(self, old_name)) + + def create_entity(self, values, key_name=None, parent=None): + """Creates a entity from a list of property values. + + Args: + values: list/tuple of str + key_name: if provided, the name for the (single) resulting entity + parent: A datastore.Key instance for the parent, or None + + Returns: + list of db.Model + + The returned entities are populated with the property values from the + argument, converted to native types using the properties map given in + the constructor, and passed through handle_entity. They're ready to be + inserted. + + Raises: + AssertionError: if the number of values doesn't match the number + of properties in the properties map. + ValueError: if any element of values is None or empty. + TypeError: if values is not a list or tuple. + """ + Validate(values, (list, tuple)) + assert len(values) == len(self.__properties), ( + 'Expected %d columns, found %d.' % + (len(self.__properties), len(values))) + + model_class = GetImplementationClass(self.kind) + + properties = { + 'key_name': key_name, + 'parent': parent, + } + for (name, converter), val in zip(self.__properties, values): + if converter is bool and val.lower() in ('0', 'false', 'no'): + val = False + properties[name] = converter(val) + + entity = model_class(**properties) + entities = self.handle_entity(entity) + + if entities: + if not isinstance(entities, (list, tuple)): + entities = [entities] + + for entity in entities: + if not isinstance(entity, db.Model): + raise TypeError('Expected a db.Model, received %s (a %s).' % + (entity, entity.__class__)) + + return entities + + def generate_key(self, i, values): + """Generates a key_name to be used in creating the underlying object. + + The default implementation returns None. + + This method can be overridden to control the key generation for + uploaded entities. The value returned should be None (to use a + server generated numeric key), or a string which neither starts + with a digit nor has the form __*__ (see + http://code.google.com/appengine/docs/python/datastore/keysandentitygroups.html), + or a datastore.Key instance. + + If you generate your own string keys, keep in mind: + + 1. The key name for each entity must be unique. + 2. If an entity of the same kind and key already exists in the + datastore, it will be overwritten. + + Args: + i: Number corresponding to this object (assume it's run in a loop, + this is your current count. + values: list/tuple of str. + + Returns: + A string to be used as the key_name for an entity. + """ + return None + + def handle_entity(self, entity): + """Subclasses can override this to add custom entity conversion code. + + This is called for each entity, after its properties are populated + from the input but before it is stored. Subclasses can override + this to add custom entity handling code. + + The entity to be inserted should be returned. If multiple entities + should be inserted, return a list of entities. If no entities + should be inserted, return None or []. + + Args: + entity: db.Model + + Returns: + db.Model or list of db.Model + """ + return entity + + def initialize(self, filename, loader_opts): + """Performs initialization and validation of the input file. + + This implementation checks that the input file exists and can be + opened for reading. + + Args: + filename: The string given as the --filename flag argument. + loader_opts: The string given as the --loader_opts flag argument. + """ + CheckFile(filename) + + def finalize(self): + """Performs finalization actions after the upload completes.""" + pass + + def generate_records(self, filename): + """Subclasses can override this to add custom data input code. + + This method must yield fixed-length lists of strings. + + The default implementation uses csv.reader to read CSV rows + from filename. + + Args: + filename: The string input for the --filename option. + + Yields: + Lists of strings. + """ + csv_generator = CSVGenerator(filename, openfile=self.__openfile, + create_csv_reader=self.__create_csv_reader + ).Records() + return csv_generator + + @staticmethod + def RegisteredLoaders(): + """Returns a dict of the Loader instances that have been created.""" + return dict(Loader.__loaders) + + @staticmethod + def RegisteredLoader(kind): + """Returns the loader instance for the given kind if it exists.""" + return Loader.__loaders[kind] + + +class RestoreThread(_ThreadBase): + """A thread to read saved entity_pbs from sqlite3.""" + NAME = 'RestoreThread' + _ENTITIES_DONE = 'Entities Done' + + def __init__(self, queue, filename): + _ThreadBase.__init__(self) + self.queue = queue + self.filename = filename + + def PerformWork(self): + db_conn = sqlite3.connect(self.filename) + cursor = db_conn.cursor() + cursor.execute('select id, value from result') + for entity_id, value in cursor: + self.queue.put(value, block=True) + self.queue.put(RestoreThread._ENTITIES_DONE, block=True) + + +class RestoreLoader(Loader): + """A Loader which imports protobuffers from a file.""" + + def __init__(self, kind, app_id): + self.kind = kind + self.app_id = app_id + + def initialize(self, filename, loader_opts): + CheckFile(filename) + self.queue = Queue.Queue(1000) + restore_thread = RestoreThread(self.queue, filename) + restore_thread.start() + self.high_id_table = self._find_high_id(self.generate_records(filename)) + restore_thread = RestoreThread(self.queue, filename) + restore_thread.start() + + def get_high_ids(self): + return dict(self.high_id_table) + + def _find_high_id(self, record_generator): + """Find the highest numeric id used for each ancestor-path, kind pair. + + Args: + record_generator: A generator of entity_encoding strings. + + Returns: + A map from ancestor-path to maps from kind to id. {path : {kind : id}} + """ + high_id = {} + for values in record_generator: + entity = self.create_entity(values) + key = entity.key() + if not key.id(): + continue + kind = key.kind() + ancestor_path = [] + if key.parent(): + ancestor_path = key.parent().to_path() + if tuple(ancestor_path) not in high_id: + high_id[tuple(ancestor_path)] = {} + kind_map = high_id[tuple(ancestor_path)] + if kind not in kind_map or kind_map[kind] < key.id(): + kind_map[kind] = key.id() + return high_id + + def generate_records(self, filename): + while True: + record = self.queue.get(block=True) + if id(record) == id(RestoreThread._ENTITIES_DONE): + break + entity_proto = entity_pb.EntityProto(contents=str(record)) + fixed_entity_proto = self._translate_entity_proto(entity_proto) + yield datastore.Entity._FromPb(fixed_entity_proto) + + def create_entity(self, values, key_name=None, parent=None): + return values + + def rewrite_reference_proto(self, reference_proto): + """Transform the Reference protobuffer which underlies keys and references. + + Args: + reference_proto: A Onestore Reference proto + """ + reference_proto.set_app(self.app_id) + + def _translate_entity_proto(self, entity_proto): + """Transform the ReferenceProperties of the given entity to fix app_id.""" + entity_key = entity_proto.mutable_key() + entity_key.set_app(self.app_id) + for prop in entity_proto.property_list(): + prop_value = prop.mutable_value() + if prop_value.has_referencevalue(): + self.rewrite_reference_proto(prop_value.mutable_referencevalue()) + + for prop in entity_proto.raw_property_list(): + prop_value = prop.mutable_value() + if prop_value.has_referencevalue(): + self.rewrite_reference_proto(prop_value.mutable_referencevalue()) + + return entity_proto + + +class Exporter(object): + """A base class for serializing datastore entities. + + To add a handler for exporting an entity kind from your datastore, + write a subclass of this class that calls Exporter.__init__ from your + class's __init__. + + If you need to run extra code to convert entities from the input + data, create new properties, or otherwise modify the entities before + they're inserted, override handle_entity. + + See the output_entities method for the writing of data from entities. + """ + + __exporters = {} + kind = None + __properties = None + calculate_sort_key_from_entity = False + + def __init__(self, kind, properties): + """Constructor. + + Populates this Exporters's kind and properties map. + + Args: + kind: a string containing the entity kind that this exporter handles + + properties: list of (name, converter, default) tuples. + + This is used to automatically convert the entities to strings. + The converter should be a function that takes one argument, a property + value of the appropriate type, and returns a str or unicode. The default + is a string to be used if the property is not present, or None to fail + with an error if the property is missing. + + For example: + [('name', str, None), + ('id_number', str, None), + ('email', str, ''), + ('user', str, None), + ('birthdate', + lambda x: str(datetime.datetime.fromtimestamp(float(x))), + None), + ('description', str, ''), + ] + """ + Validate(kind, basestring) + self.kind = kind + + GetImplementationClass(kind) + + Validate(properties, list) + for name, fn, default in properties: + Validate(name, basestring) + assert callable(fn), ( + 'Conversion function %s for property %s is not callable.' % ( + fn, name)) + if default: + Validate(default, basestring) + + self.__properties = properties + + @staticmethod + def RegisterExporter(exporter): + """Register exporter and the Exporter instance for its kind. + + Args: + exporter: A Exporter instance. + """ + Exporter.__exporters[exporter.kind] = exporter + + def __ExtractProperties(self, entity): + """Converts an entity into a list of string values. + + Args: + entity: An entity to extract the properties from. + + Returns: + A list of the properties of the entity. + + Raises: + MissingPropertyError: if an expected field on the entity is missing. + """ + encoding = [] + for name, fn, default in self.__properties: + try: + encoding.append(fn(entity[name])) + except KeyError: + if name == '__key__': + encoding.append(fn(entity.key())) + elif default is None: + raise MissingPropertyError(name) + else: + encoding.append(default) + return encoding + + def __EncodeEntity(self, entity): + """Convert the given entity into CSV string. + + Args: + entity: The entity to encode. + + Returns: + A CSV string. + """ + output = StringIO.StringIO() + writer = csv.writer(output) + writer.writerow(self.__ExtractProperties(entity)) + return output.getvalue() + + def __SerializeEntity(self, entity): + """Creates a string representation of an entity. + + Args: + entity: The entity to serialize. + + Returns: + A serialized representation of an entity. + """ + encoding = self.__EncodeEntity(entity) + if not isinstance(encoding, unicode): + encoding = unicode(encoding, 'utf-8') + encoding = encoding.encode('utf-8') + return encoding + + def output_entities(self, entity_generator): + """Outputs the downloaded entities. + + This implementation writes CSV. + + Args: + entity_generator: A generator that yields the downloaded entities + in key order. + """ + CheckOutputFile(self.output_filename) + output_file = open(self.output_filename, 'w') + logger.debug('Export complete, writing to file') + output_file.writelines(self.__SerializeEntity(entity) + for entity in entity_generator) + + def initialize(self, filename, exporter_opts): + """Performs initialization and validation of the output file. + + This implementation checks that the input file exists and can be + opened for writing. + + Args: + filename: The string given as the --filename flag argument. + exporter_opts: The string given as the --exporter_opts flag argument. + """ + CheckOutputFile(filename) + self.output_filename = filename + + def finalize(self): + """Performs finalization actions after the download completes.""" + pass + + def sort_key_from_entity(self, entity): + """A value to alter sorting of entities in output_entities entity_generator. + + Will only be called if calculate_sort_key_from_entity is true. + Args: + entity: A datastore.Entity. + Returns: + A value to store in the intermediate sqlite table. The table will later + be sorted by this value then by the datastore key, so the sort_key need + not be unique. + """ + return '' + + @staticmethod + def RegisteredExporters(): + """Returns a dictionary of the exporter instances that have been created.""" + return dict(Exporter.__exporters) + + @staticmethod + def RegisteredExporter(kind): + """Returns an exporter instance for the given kind if it exists.""" + return Exporter.__exporters[kind] + + +class DumpExporter(Exporter): + """An exporter which dumps protobuffers to a file.""" + + def __init__(self, kind, result_db_filename): + self.kind = kind + self.result_db_filename = result_db_filename + + def output_entities(self, entity_generator): + shutil.copyfile(self.result_db_filename, self.output_filename) + + +class MapperRetry(Error): + """An exception that indicates a non-fatal error during mapping.""" + + +class Mapper(object): + """A base class for serializing datastore entities. + + To add a handler for exporting an entity kind from your datastore, + write a subclass of this class that calls Mapper.__init__ from your + class's __init__. + + You need to implement to batch_apply or apply method on your subclass + for the map to do anything. + """ + + __mappers = {} + kind = None + + def __init__(self, kind): + """Constructor. + + Populates this Mappers's kind. + + Args: + kind: a string containing the entity kind that this mapper handles + """ + Validate(kind, basestring) + self.kind = kind + + GetImplementationClass(kind) + + @staticmethod + def RegisterMapper(mapper): + """Register mapper and the Mapper instance for its kind. + + Args: + mapper: A Mapper instance. + """ + Mapper.__mappers[mapper.kind] = mapper + + def initialize(self, mapper_opts): + """Performs initialization. + + Args: + mapper_opts: The string given as the --mapper_opts flag argument. + """ + pass + + def finalize(self): + """Performs finalization actions after the download completes.""" + pass + + def apply(self, entity): + print 'Default map function doing nothing to %s' % entity + + def batch_apply(self, entities): + for entity in entities: + self.apply(entity) + + def map_over_keys_only(self): + """Return whether this mapper should iterate over only keys or not. + + Override this method in subclasses to return True values. + + Returns: + True or False + """ + return False + + @staticmethod + def RegisteredMappers(): + """Returns a dictionary of the mapper instances that have been created.""" + return dict(Mapper.__mappers) + + @staticmethod + def RegisteredMapper(kind): + """Returns an mapper instance for the given kind if it exists.""" + return Mapper.__mappers[kind] + + +class QueueJoinThread(threading.Thread): + """A thread that joins a queue and exits. + + Queue joins do not have a timeout. To simulate a queue join with + timeout, run this thread and join it with a timeout. + """ + + def __init__(self, queue): + """Initialize a QueueJoinThread. + + Args: + queue: The queue for this thread to join. + """ + threading.Thread.__init__(self) + assert isinstance(queue, (Queue.Queue, ReQueue)) + self.queue = queue + + def run(self): + """Perform the queue join in this thread.""" + self.queue.join() + + +def InterruptibleQueueJoin(queue, + thread_local, + thread_pool, + queue_join_thread_factory=QueueJoinThread, + check_workers=True): + """Repeatedly joins the given ReQueue or Queue.Queue with short timeout. + + Between each timeout on the join, worker threads are checked. + + Args: + queue: A Queue.Queue or ReQueue instance. + thread_local: A threading.local instance which indicates interrupts. + thread_pool: An AdaptiveThreadPool instance. + queue_join_thread_factory: Used for dependency injection. + check_workers: Whether to interrupt the join on worker death. + + Returns: + True unless the queue join is interrupted by SIGINT or worker death. + """ + thread = queue_join_thread_factory(queue) + thread.start() + while True: + thread.join(timeout=.5) + if not thread.isAlive(): + return True + if thread_local.shut_down: + logger.debug('Queue join interrupted') + return False + if check_workers: + for worker_thread in thread_pool.Threads(): + if not worker_thread.isAlive(): + return False + + +def ShutdownThreads(data_source_thread, thread_pool): + """Shuts down the worker and data source threads. + + Args: + data_source_thread: A running DataSourceThread instance. + thread_pool: An AdaptiveThreadPool instance with workers registered. + """ + logger.info('An error occurred. Shutting down...') + + data_source_thread.exit_flag = True + + thread_pool.Shutdown() + + data_source_thread.join(timeout=3.0) + if data_source_thread.isAlive(): + logger.warn('%s hung while trying to exit', + data_source_thread.GetFriendlyName()) + + +class BulkTransporterApp(object): + """Class to wrap bulk transport application functionality.""" + + def __init__(self, + arg_dict, + input_generator_factory, + throttle, + progress_db, + progresstrackerthread_factory, + max_queue_size=DEFAULT_QUEUE_SIZE, + request_manager_factory=RequestManager, + datasourcethread_factory=DataSourceThread, + progress_queue_factory=Queue.Queue, + thread_pool_factory=adaptive_thread_pool.AdaptiveThreadPool): + """Instantiate a BulkTransporterApp. + + Uploads or downloads data to or from application using HTTP requests. + When run, the class will spin up a number of threads to read entities + from the data source, pass those to a number of worker threads + for sending to the application, and track all of the progress in a + small database in case an error or pause/termination requires a + restart/resumption of the upload process. + + Args: + arg_dict: Dictionary of command line options. + input_generator_factory: A factory that creates a WorkItem generator. + throttle: A Throttle instance. + progress_db: The database to use for replaying/recording progress. + progresstrackerthread_factory: Used for dependency injection. + max_queue_size: Maximum size of the queues before they should block. + request_manager_factory: Used for dependency injection. + datasourcethread_factory: Used for dependency injection. + progress_queue_factory: Used for dependency injection. + thread_pool_factory: Used for dependency injection. + """ + self.app_id = arg_dict['application'] + self.post_url = arg_dict['url'] + self.kind = arg_dict['kind'] + self.batch_size = arg_dict['batch_size'] + self.input_generator_factory = input_generator_factory + self.num_threads = arg_dict['num_threads'] + self.email = arg_dict['email'] + self.passin = arg_dict['passin'] + self.dry_run = arg_dict['dry_run'] + self.throttle = throttle + self.progress_db = progress_db + self.progresstrackerthread_factory = progresstrackerthread_factory + self.max_queue_size = max_queue_size + self.request_manager_factory = request_manager_factory + self.datasourcethread_factory = datasourcethread_factory + self.progress_queue_factory = progress_queue_factory + self.thread_pool_factory = thread_pool_factory + (scheme, + self.host_port, self.url_path, + unused_query, unused_fragment) = urlparse.urlsplit(self.post_url) + self.secure = (scheme == 'https') + + def RunPostAuthentication(self): + """Method that gets called after authentication.""" + if isinstance(self.kind, basestring): + return [self.kind] + return self.kind + + def Run(self): + """Perform the work of the BulkTransporterApp. + + Raises: + AuthenticationError: If authentication is required and fails. + + Returns: + Error code suitable for sys.exit, e.g. 0 on success, 1 on failure. + """ + self.error = False + thread_pool = self.thread_pool_factory( + self.num_threads, queue_size=self.max_queue_size) + + progress_queue = self.progress_queue_factory(self.max_queue_size) + self.request_manager = self.request_manager_factory(self.app_id, + self.host_port, + self.url_path, + self.kind, + self.throttle, + self.batch_size, + self.secure, + self.email, + self.passin, + self.dry_run) + try: + self.request_manager.Authenticate() + except Exception, e: + self.error = True + if not isinstance(e, urllib2.HTTPError) or ( + e.code != 302 and e.code != 401): + logger.exception('Exception during authentication') + raise AuthenticationError() + if (self.request_manager.auth_called and + not self.request_manager.authenticated): + self.error = True + raise AuthenticationError('Authentication failed') + + kinds = self.RunPostAuthentication() + + for thread in thread_pool.Threads(): + self.throttle.Register(thread) + + self.progress_thread = self.progresstrackerthread_factory( + progress_queue, self.progress_db) + + if self.progress_db.UseProgressData(): + logger.debug('Restarting upload using progress database') + progress_generator_factory = self.progress_db.GetProgressStatusGenerator + else: + progress_generator_factory = None + + self.data_source_thread = ( + self.datasourcethread_factory(self.request_manager, + kinds, + thread_pool, + progress_queue, + self.input_generator_factory, + progress_generator_factory)) + + self.throttle.Register(self.data_source_thread) + + thread_local = threading.local() + thread_local.shut_down = False + + def Interrupt(unused_signum, unused_frame): + """Shutdown gracefully in response to a signal.""" + thread_local.shut_down = True + self.error = True + + signal.signal(signal.SIGINT, Interrupt) + + self.progress_thread.start() + self.data_source_thread.start() + + + while not thread_local.shut_down: + self.data_source_thread.join(timeout=0.25) + + if self.data_source_thread.isAlive(): + for thread in list(thread_pool.Threads()) + [self.progress_thread]: + if not thread.isAlive(): + logger.info('Unexpected thread death: %s', thread.getName()) + thread_local.shut_down = True + self.error = True + break + else: + break + + def _Join(ob, msg): + logger.debug('Waiting for %s...', msg) + if isinstance(ob, threading.Thread): + ob.join(timeout=3.0) + if ob.isAlive(): + logger.debug('Joining %s failed', ob) + else: + logger.debug('... done.') + elif isinstance(ob, (Queue.Queue, ReQueue)): + if not InterruptibleQueueJoin(ob, thread_local, thread_pool): + ShutdownThreads(self.data_source_thread, thread_pool) + else: + ob.join() + logger.debug('... done.') + + if self.data_source_thread.error or thread_local.shut_down: + ShutdownThreads(self.data_source_thread, thread_pool) + else: + _Join(thread_pool.requeue, 'worker threads to finish') + + thread_pool.Shutdown() + thread_pool.JoinThreads() + thread_pool.CheckErrors() + print '' + + if self.progress_thread.isAlive(): + InterruptibleQueueJoin(progress_queue, thread_local, thread_pool, + check_workers=False) + else: + logger.warn('Progress thread exited prematurely') + + progress_queue.put(_THREAD_SHOULD_EXIT) + _Join(self.progress_thread, 'progress_thread to terminate') + self.progress_thread.CheckError() + if not thread_local.shut_down: + self.progress_thread.WorkFinished() + + self.data_source_thread.CheckError() + + return self.ReportStatus() + + def ReportStatus(self): + """Display a message reporting the final status of the transfer.""" + raise NotImplementedError() + + +class BulkUploaderApp(BulkTransporterApp): + """Class to encapsulate bulk uploader functionality.""" + + def __init__(self, *args, **kwargs): + BulkTransporterApp.__init__(self, *args, **kwargs) + + def RunPostAuthentication(self): + loader = Loader.RegisteredLoader(self.kind) + high_id_table = loader.get_high_ids() + for ancestor_path, kind_map in high_id_table.iteritems(): + for kind, high_id in kind_map.iteritems(): + self.request_manager.IncrementId(list(ancestor_path), kind, high_id) + return [self.kind] + + def ReportStatus(self): + """Display a message reporting the final status of the transfer.""" + total_up, duration = self.throttle.TotalTransferred( + remote_api_throttle.BANDWIDTH_UP) + s_total_up, unused_duration = self.throttle.TotalTransferred( + remote_api_throttle.HTTPS_BANDWIDTH_UP) + total_up += s_total_up + total = total_up + logger.info('%d entites total, %d previously transferred', + self.data_source_thread.read_count, + self.data_source_thread.xfer_count) + transfer_count = self.progress_thread.EntitiesTransferred() + logger.info('%d entities (%d bytes) transferred in %.1f seconds', + transfer_count, total, duration) + if (self.data_source_thread.read_all and + transfer_count + + self.data_source_thread.xfer_count >= + self.data_source_thread.read_count): + logger.info('All entities successfully transferred') + return 0 + else: + logger.info('Some entities not successfully transferred') + return 1 + + +class BulkDownloaderApp(BulkTransporterApp): + """Class to encapsulate bulk downloader functionality.""" + + def __init__(self, *args, **kwargs): + BulkTransporterApp.__init__(self, *args, **kwargs) + + def RunPostAuthentication(self): + if not self.kind: + return self.request_manager.GetSchemaKinds() + elif isinstance(self.kind, basestring): + return [self.kind] + else: + return self.kind + + def ReportStatus(self): + """Display a message reporting the final status of the transfer.""" + total_down, duration = self.throttle.TotalTransferred( + remote_api_throttle.BANDWIDTH_DOWN) + s_total_down, unused_duration = self.throttle.TotalTransferred( + remote_api_throttle.HTTPS_BANDWIDTH_DOWN) + total_down += s_total_down + total = total_down + existing_count = self.progress_thread.existing_count + xfer_count = self.progress_thread.EntitiesTransferred() + logger.info('Have %d entities, %d previously transferred', + xfer_count, existing_count) + logger.info('%d entities (%d bytes) transferred in %.1f seconds', + xfer_count, total, duration) + if self.error: + return 1 + else: + return 0 + + +class BulkMapperApp(BulkTransporterApp): + """Class to encapsulate bulk map functionality.""" + + def __init__(self, *args, **kwargs): + BulkTransporterApp.__init__(self, *args, **kwargs) + + def ReportStatus(self): + """Display a message reporting the final status of the transfer.""" + total_down, duration = self.throttle.TotalTransferred( + remote_api_throttle.BANDWIDTH_DOWN) + s_total_down, unused_duration = self.throttle.TotalTransferred( + remote_api_throttle.HTTPS_BANDWIDTH_DOWN) + total_down += s_total_down + total = total_down + xfer_count = self.progress_thread.EntitiesTransferred() + logger.info('The following may be inaccurate if any mapper tasks ' + 'encountered errors and had to be retried.') + logger.info('Applied mapper to %s entities.', + xfer_count) + logger.info('%s entities (%s bytes) transferred in %.1f seconds', + xfer_count, total, duration) + if self.error: + return 1 + else: + return 0 + + +def PrintUsageExit(code): + """Prints usage information and exits with a status code. + + Args: + code: Status code to pass to sys.exit() after displaying usage information. + """ + print __doc__ % {'arg0': sys.argv[0]} + sys.stdout.flush() + sys.stderr.flush() + sys.exit(code) + + +REQUIRED_OPTION = object() + +BOOL_ARGS = ('create_config', 'debug', 'download', 'dry_run', 'dump', + 'has_header', 'map', 'passin', 'restore') +INT_ARGS = ('bandwidth_limit', 'batch_size', 'http_limit', 'num_threads', + 'rps_limit') +FILENAME_ARGS = ('config_file', 'db_filename', 'filename', 'log_file', + 'result_db_filename') +STRING_ARGS = ('application', 'auth_domain', 'email', 'exporter_opts', + 'kind', 'loader_opts', 'mapper_opts', 'namespace', 'url') +DEPRECATED_OPTIONS = {'csv_has_header': 'has_header', 'app_id': 'application'} +FLAG_SPEC = (['csv_has_header', 'help', 'app_id='] + + list(BOOL_ARGS) + + [arg + '=' for arg in INT_ARGS + FILENAME_ARGS + STRING_ARGS]) + +def ParseArguments(argv, die_fn=lambda: PrintUsageExit(1)): + """Parses command-line arguments. + + Prints out a help message if -h or --help is supplied. + + Args: + argv: List of command-line arguments. + die_fn: Function to invoke to end the program. + + Returns: + A dictionary containing the value of command-line options. + """ + opts, unused_args = getopt.getopt( + argv[1:], + 'h', + FLAG_SPEC) + + arg_dict = {} + + arg_dict['url'] = REQUIRED_OPTION + arg_dict['filename'] = None + arg_dict['config_file'] = None + arg_dict['kind'] = None + + arg_dict['batch_size'] = None + arg_dict['num_threads'] = DEFAULT_THREAD_COUNT + arg_dict['bandwidth_limit'] = DEFAULT_BANDWIDTH_LIMIT + arg_dict['rps_limit'] = DEFAULT_RPS_LIMIT + arg_dict['http_limit'] = DEFAULT_REQUEST_LIMIT + + arg_dict['application'] = '' + arg_dict['auth_domain'] = 'gmail.com' + arg_dict['create_config'] = False + arg_dict['db_filename'] = None + arg_dict['debug'] = False + arg_dict['download'] = False + arg_dict['dry_run'] = False + arg_dict['dump'] = False + arg_dict['email'] = None + arg_dict['exporter_opts'] = None + arg_dict['has_header'] = False + arg_dict['loader_opts'] = None + arg_dict['log_file'] = None + arg_dict['map'] = False + arg_dict['mapper_opts'] = None + arg_dict['namespace'] = '' + arg_dict['passin'] = False + arg_dict['restore'] = False + arg_dict['result_db_filename'] = None + + def ExpandFilename(filename): + """Expand shell variables and ~usernames in filename.""" + return os.path.expandvars(os.path.expanduser(filename)) + + for option, value in opts: + if option in ('-h', '--help'): + PrintUsageExit(0) + if not option.startswith('--'): + continue + option = option[2:] + if option in DEPRECATED_OPTIONS: + print >>sys.stderr, ('--%s is deprecated, please use --%s.' % + (option, DEPRECATED_OPTIONS[option])) + option = DEPRECATED_OPTIONS[option] + + if option in BOOL_ARGS: + arg_dict[option] = True + elif option in INT_ARGS: + arg_dict[option] = int(value) + elif option in FILENAME_ARGS: + arg_dict[option] = ExpandFilename(value) + elif option in STRING_ARGS: + arg_dict[option] = value + + return ProcessArguments(arg_dict, die_fn=die_fn) + + +def ThrottleLayout(bandwidth_limit, http_limit, rps_limit): + """Return a dictionary indicating the throttle options.""" + bulkloader_limits = dict(remote_api_throttle.NO_LIMITS) + bulkloader_limits.update({ + remote_api_throttle.BANDWIDTH_UP: bandwidth_limit, + remote_api_throttle.BANDWIDTH_DOWN: bandwidth_limit, + remote_api_throttle.REQUESTS: http_limit, + remote_api_throttle.HTTPS_BANDWIDTH_UP: bandwidth_limit, + remote_api_throttle.HTTPS_BANDWIDTH_DOWN: bandwidth_limit, + remote_api_throttle.HTTPS_REQUESTS: http_limit, + remote_api_throttle.ENTITIES_FETCHED: rps_limit, + remote_api_throttle.ENTITIES_MODIFIED: rps_limit, + }) + return bulkloader_limits + + +def CheckOutputFile(filename): + """Check that the given file does not exist and can be opened for writing. + + Args: + filename: The name of the file. + + Raises: + FileExistsError: if the given filename is not found + FileNotWritableError: if the given filename is not readable. + """ + full_path = os.path.abspath(filename) + if os.path.exists(full_path): + raise FileExistsError('%s: output file exists' % filename) + elif not os.access(os.path.dirname(full_path), os.W_OK): + raise FileNotWritableError( + '%s: not writable' % os.path.dirname(full_path)) + + +def LoadYamlConfig(config_file_name): + """Loads a config file and registers any Loader classes present. + + Used for a the second generation Yaml configuration file. + + Args: + config_file_name: The name of the configuration file. + """ + (loaders, exporters) = bulkloader_config.load_config(config_file_name) + for cls in loaders: + Loader.RegisterLoader(cls()) + for cls in exporters: + Exporter.RegisterExporter(cls()) + + +def LoadConfig(config_file_name, exit_fn=sys.exit): + """Loads a config file and registers any Loader classes present. + + Used for a legacy Python configuration file. + + Args: + config_file_name: The name of the configuration file. + exit_fn: Used for dependency injection. + """ + if config_file_name: + config_file = open(config_file_name, 'r') + try: + bulkloader_config = imp.load_module( + 'bulkloader_config', config_file, config_file_name, + ('', 'r', imp.PY_SOURCE)) + sys.modules['bulkloader_config'] = bulkloader_config + + if hasattr(bulkloader_config, 'loaders'): + for cls in bulkloader_config.loaders: + Loader.RegisterLoader(cls()) + + if hasattr(bulkloader_config, 'exporters'): + for cls in bulkloader_config.exporters: + Exporter.RegisterExporter(cls()) + + if hasattr(bulkloader_config, 'mappers'): + for cls in bulkloader_config.mappers: + Mapper.RegisterMapper(cls()) + + except NameError, e: + m = re.search(r"[^']*'([^']*)'.*", str(e)) + if m.groups() and m.group(1) == 'Loader': + print >>sys.stderr, """ +The config file format has changed and you appear to be using an old-style +config file. Please make the following changes: + +1. At the top of the file, add this: + +from google.appengine.tools.bulkloader import Loader + +2. For each of your Loader subclasses add the following at the end of the + __init__ definitioion: + +self.alias_old_names() + +3. At the bottom of the file, add this: + +loaders = [MyLoader1,...,MyLoaderN] + +Where MyLoader1,...,MyLoaderN are the Loader subclasses you want the bulkloader +to have access to. +""" + exit_fn(1) + else: + raise + except Exception, e: + if isinstance(e, NameClashError) or 'bulkloader_config' in vars() and ( + hasattr(bulkloader_config, 'bulkloader') and + isinstance(e, bulkloader_config.bulkloader.NameClashError)): + print >> sys.stderr, ( + 'Found both %s and %s while aliasing old names on %s.'% + (e.old_name, e.new_name, e.klass)) + exit_fn(1) + else: + raise + +def GetArgument(kwargs, name, die_fn): + """Get the value of the key name in kwargs, or die with die_fn. + + Args: + kwargs: A dictionary containing the options for the bulkloader. + name: The name of a bulkloader option. + die_fn: The function to call to exit the program. + + Returns: + The value of kwargs[name] is name in kwargs + """ + if name in kwargs: + return kwargs[name] + else: + print >>sys.stderr, '%s argument required' % name + die_fn() + + +def _MakeSignature(app_id=None, + url=None, + kind=None, + db_filename=None, + perform_map=None, + download=None, + has_header=None, + result_db_filename=None, + dump=None, + restore=None): + """Returns a string that identifies the important options for the database.""" + if download: + result_db_line = 'result_db: %s' % result_db_filename + else: + result_db_line = '' + return u""" + app_id: %s + url: %s + kind: %s + download: %s + map: %s + dump: %s + restore: %s + progress_db: %s + has_header: %s + %s + """ % (app_id, url, kind, download, perform_map, dump, restore, db_filename, + has_header, result_db_line) + + +def ProcessArguments(arg_dict, + die_fn=lambda: sys.exit(1)): + """Processes non command-line input arguments. + + Args: + arg_dict: Dictionary containing the values of bulkloader options. + die_fn: Function to call in case of an error during argument processing. + + Returns: + A dictionary of bulkloader options. + """ + application = GetArgument(arg_dict, 'application', die_fn) + url = GetArgument(arg_dict, 'url', die_fn) + dump = GetArgument(arg_dict, 'dump', die_fn) + restore = GetArgument(arg_dict, 'restore', die_fn) + create_config = GetArgument(arg_dict, 'create_config', die_fn) + filename = GetArgument(arg_dict, 'filename', die_fn) + batch_size = GetArgument(arg_dict, 'batch_size', die_fn) + kind = GetArgument(arg_dict, 'kind', die_fn) + db_filename = GetArgument(arg_dict, 'db_filename', die_fn) + config_file = GetArgument(arg_dict, 'config_file', die_fn) + result_db_filename = GetArgument(arg_dict, 'result_db_filename', die_fn) + download = GetArgument(arg_dict, 'download', die_fn) + log_file = GetArgument(arg_dict, 'log_file', die_fn) + perform_map = GetArgument(arg_dict, 'map', die_fn) + namespace = GetArgument(arg_dict, 'namespace', die_fn) + + errors = [] + + if batch_size is None: + if download or perform_map or dump or create_config: + arg_dict['batch_size'] = DEFAULT_DOWNLOAD_BATCH_SIZE + else: + arg_dict['batch_size'] = DEFAULT_BATCH_SIZE + elif batch_size <= 0: + errors.append('batch_size must be at least 1') + + if db_filename is None: + arg_dict['db_filename'] = time.strftime( + 'bulkloader-progress-%Y%m%d.%H%M%S.sql3') + + if result_db_filename is None: + arg_dict['result_db_filename'] = time.strftime( + 'bulkloader-results-%Y%m%d.%H%M%S.sql3') + + if log_file is None: + arg_dict['log_file'] = time.strftime('bulkloader-log-%Y%m%d.%H%M%S') + + required = '%s argument required' + + if config_file is None and not dump and not restore and not create_config: + errors.append('One of --config_file, --dump, --restore, or --create_config ' + 'is required') + + if url is REQUIRED_OPTION: + errors.append(required % 'url') + + if not filename and not perform_map: + errors.append(required % 'filename') + + if not kind: + if download or perform_map: + errors.append('kind argument required for this operation') + elif not dump and not restore and not create_config: + errors.append( + 'kind argument required unless --dump, --restore or --create_config ' + 'specified') + + if namespace: + try: + namespace_manager.validate_namespace(namespace) + except namespace_manager.BadValueError, msg: + errors.append('namespace parameter %s' % msg) + + if not application: + if url and url is not REQUIRED_OPTION: + (unused_scheme, host_port, unused_url_path, + unused_query, unused_fragment) = urlparse.urlsplit(url) + suffix_idx = host_port.find('.appspot.com') + if suffix_idx > -1: + arg_dict['application'] = host_port[:suffix_idx] + elif host_port.split(':')[0].endswith('google.com'): + arg_dict['application'] = host_port.split('.')[0] + else: + errors.append('application argument required for non appspot.com domains') + + POSSIBLE_COMMANDS = ('create_config', 'download', 'dump', 'map', 'restore') + commands = [] + for command in POSSIBLE_COMMANDS: + if arg_dict[command]: + commands.append(command) + if len(commands) > 1: + errors.append('%s are mutually exclusive.' % ' and '.join(commands)) + + + if errors: + print >>sys.stderr, '\n'.join(errors) + die_fn() + + return arg_dict + + +def ParseKind(kind): + if kind and kind[0] == '(' and kind[-1] == ')': + return tuple(kind[1:-1].split(',')) + else: + return kind + + +def _PerformBulkload(arg_dict, + check_file=CheckFile, + check_output_file=CheckOutputFile): + """Runs the bulkloader, given the command line options. + + Args: + arg_dict: Dictionary of bulkloader options. + check_file: Used for dependency injection. + check_output_file: Used for dependency injection. + + Returns: + An exit code. + + Raises: + ConfigurationError: if inconsistent options are passed. + """ + app_id = arg_dict['application'] + url = arg_dict['url'] + filename = arg_dict['filename'] + batch_size = arg_dict['batch_size'] + kind = arg_dict['kind'] + num_threads = arg_dict['num_threads'] + bandwidth_limit = arg_dict['bandwidth_limit'] + rps_limit = arg_dict['rps_limit'] + http_limit = arg_dict['http_limit'] + db_filename = arg_dict['db_filename'] + config_file = arg_dict['config_file'] + auth_domain = arg_dict['auth_domain'] + has_header = arg_dict['has_header'] + download = arg_dict['download'] + result_db_filename = arg_dict['result_db_filename'] + loader_opts = arg_dict['loader_opts'] + exporter_opts = arg_dict['exporter_opts'] + mapper_opts = arg_dict['mapper_opts'] + email = arg_dict['email'] + passin = arg_dict['passin'] + perform_map = arg_dict['map'] + dump = arg_dict['dump'] + restore = arg_dict['restore'] + create_config = arg_dict['create_config'] + namespace = arg_dict['namespace'] + + if namespace: + namespace_manager.set_namespace(namespace) + os.environ['AUTH_DOMAIN'] = auth_domain + + kind = ParseKind(kind) + + if not dump and not restore and not create_config: + check_file(config_file) + + if download or dump or create_config: + check_output_file(filename) + elif not perform_map: + check_file(filename) + + throttle_layout = ThrottleLayout(bandwidth_limit, http_limit, rps_limit) + logger.info('Throttling transfers:') + logger.info('Bandwidth: %s bytes/second', bandwidth_limit) + logger.info('HTTP connections: %s/second', http_limit) + logger.info('Entities inserted/fetched/modified: %s/second', rps_limit) + logger.info('Batch Size: %s', batch_size) + + throttle = remote_api_throttle.Throttle(layout=throttle_layout) + + throttle.Register(threading.currentThread()) + threading.currentThread().exit_flag = False + + if dump: + Exporter.RegisterExporter(DumpExporter(kind, result_db_filename)) + elif restore: + Loader.RegisterLoader(RestoreLoader(kind, app_id)) + elif create_config: + kind = '__Stat_PropertyType_PropertyName_Kind__' + arg_dict['kind'] = kind + root_dir = os.path.dirname(os.path.abspath(__file__)) + if os.path.basename(root_dir) == 'tools': + root_dir = os.path.dirname(os.path.dirname(os.path.dirname(root_dir))) + LoadYamlConfig(os.path.join(root_dir, 'google', 'appengine', 'ext', + 'bulkload', 'bulkloader_wizard.yaml')) + elif (config_file and + (config_file.endswith('.yaml') or config_file.endswith('.yml'))): + LoadYamlConfig(config_file) + else: + LoadConfig(config_file) + + os.environ['APPLICATION_ID'] = app_id + + signature = _MakeSignature(app_id=app_id, + url=url, + kind=kind, + db_filename=db_filename, + download=download, + perform_map=perform_map, + has_header=has_header, + result_db_filename=result_db_filename, + dump=dump, + restore=restore) + + max_queue_size = max(DEFAULT_QUEUE_SIZE, 3 * num_threads + 5) + + upload = not (download or dump or restore or perform_map or create_config) + + if db_filename == 'skip': + progress_db = StubProgressDatabase() + elif upload or restore: + progress_db = ProgressDatabase(db_filename, signature) + else: + progress_db = ExportProgressDatabase(db_filename, signature) + + return_code = 1 + + if upload or restore: + loader = Loader.RegisteredLoader(kind) + try: + loader.initialize(filename, loader_opts) + workitem_generator_factory = GetCSVGeneratorFactory( + kind, filename, batch_size, has_header) + + app = BulkUploaderApp(arg_dict, + workitem_generator_factory, + throttle, + progress_db, + ProgressTrackerThread, + max_queue_size, + RequestManager, + DataSourceThread, + Queue.Queue) + try: + return_code = app.Run() + except AuthenticationError: + logger.info('Authentication Failed') + finally: + loader.finalize() + elif download or dump or create_config: + exporter = Exporter.RegisteredExporter(kind) + result_db = ResultDatabase(result_db_filename, signature, exporter=exporter) + try: + exporter.initialize(filename, exporter_opts) + + def KeyRangeGeneratorFactory(request_manager, progress_queue, + progress_gen, kinds): + logger.info('Downloading kinds: %s', kinds) + return KeyRangeItemGenerator(request_manager, kinds, progress_queue, + progress_gen, DownloadItem) + + def ExportProgressThreadFactory(progress_queue, progress_db): + return ExportProgressThread(exporter, + progress_queue, + progress_db, + result_db) + + app = BulkDownloaderApp(arg_dict, + KeyRangeGeneratorFactory, + throttle, + progress_db, + ExportProgressThreadFactory, + 0, + RequestManager, + DataSourceThread, + Queue.Queue) + try: + return_code = app.Run() + except AuthenticationError: + logger.info('Authentication Failed') + except KindStatError: + logger.error('Unable to download kind stats for all-kinds download.') + logger.error('Kind stats are generated periodically by the appserver') + logger.error('Kind stats are not available on dev_appserver.') + finally: + exporter.finalize() + elif perform_map: + mapper = Mapper.RegisteredMapper(kind) + try: + mapper.initialize(mapper_opts) + + def KeyRangeGeneratorFactory(request_manager, progress_queue, + progress_gen, kinds): + return KeyRangeItemGenerator(request_manager, kinds, progress_queue, + progress_gen, MapperItem) + + def MapperProgressThreadFactory(progress_queue, progress_db): + return MapperProgressThread(mapper, + progress_queue, + progress_db) + + app = BulkMapperApp(arg_dict, + KeyRangeGeneratorFactory, + throttle, + progress_db, + MapperProgressThreadFactory, + 0, + RequestManager, + DataSourceThread, + Queue.Queue) + try: + return_code = app.Run() + except AuthenticationError: + logger.info('Authentication Failed') + finally: + mapper.finalize() + return return_code + + +def SetupLogging(arg_dict): + """Sets up logging for the bulkloader. + + Args: + arg_dict: Dictionary mapping flag names to their arguments. + """ + format = '[%(levelname)-8s %(asctime)s %(filename)s] %(message)s' + debug = arg_dict['debug'] + log_file = arg_dict['log_file'] + + logger.setLevel(logging.DEBUG) + + logger.propagate = False + + file_handler = logging.FileHandler(log_file, 'w') + file_handler.setLevel(logging.DEBUG) + file_formatter = logging.Formatter(format) + file_handler.setFormatter(file_formatter) + logger.addHandler(file_handler) + + console = logging.StreamHandler() + level = logging.INFO + if debug: + level = logging.DEBUG + console.setLevel(level) + console_format = '[%(levelname)-8s] %(message)s' + formatter = logging.Formatter(console_format) + console.setFormatter(formatter) + logger.addHandler(console) + + logger.info('Logging to %s', log_file) + + remote_api_throttle.logger.setLevel(level) + remote_api_throttle.logger.addHandler(file_handler) + remote_api_throttle.logger.addHandler(console) + + appengine_rpc.logger.setLevel(logging.WARN) + + adaptive_thread_pool.logger.setLevel(logging.DEBUG) + adaptive_thread_pool.logger.addHandler(console) + adaptive_thread_pool.logger.addHandler(file_handler) + adaptive_thread_pool.logger.propagate = False + + +def Run(arg_dict): + """Sets up and runs the bulkloader, given the options as keyword arguments. + + Args: + arg_dict: Dictionary of bulkloader options + + Returns: + An exit code. + """ + arg_dict = ProcessArguments(arg_dict) + + SetupLogging(arg_dict) + + return _PerformBulkload(arg_dict) + + +def main(argv): + """Runs the importer from the command line.""" + + arg_dict = ParseArguments(argv) + + errors = ['%s argument required' % key + for (key, value) in arg_dict.iteritems() + if value is REQUIRED_OPTION] + if errors: + print >>sys.stderr, '\n'.join(errors) + PrintUsageExit(1) + + SetupLogging(arg_dict) + return _PerformBulkload(arg_dict) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/google_appengine/google/appengine/tools/bulkloader.pyc b/google_appengine/google/appengine/tools/bulkloader.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46dba223a4a9bcb696070e4ff8bfcc170198f815 GIT binary patch literal 144762 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 StGQkB_aEnuOSz$b|NjBp*`%ug literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev-channel-js.js b/google_appengine/google/appengine/tools/dev-channel-js.js new file mode 100755 index 0000000..1975072 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev-channel-js.js @@ -0,0 +1,5633 @@ +(function() { var a, goog = goog || {}; +goog.global = this; +goog.DEBUG = true; +goog.LOCALE = "en"; +goog.evalWorksForGlobals_ = null; +goog.provide = function(name) { + goog.exportPath_(name) +}; +goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { + var parts = name.split("."), cur = opt_objectToExportTo || goog.global; + !(parts[0] in cur) && cur.execScript && cur.execScript("var " + parts[0]); + for(var part;parts.length && (part = parts.shift());) { + if(!parts.length && goog.isDef(opt_object)) { + cur[part] = opt_object + }else { + cur = cur[part] ? cur[part] : cur[part] = {} + } + } +}; +goog.getObjectByName = function(name, opt_obj) { + var parts = name.split("."), cur = opt_obj || goog.global; + for(var part;part = parts.shift();) { + if(cur[part]) { + cur = cur[part] + }else { + return null + } + } + return cur +}; +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for(var x in obj) { + global[x] = obj[x] + } +}; +goog.addDependency = function() { +}; +goog.useStrictRequires = false; +goog.require = function() { +}; +goog.basePath = ""; +goog.nullFunction = function() { +}; +goog.identityFunction = function() { + return arguments[0] +}; +goog.abstractMethod = function() { + throw Error("unimplemented abstract method"); +}; +goog.addSingletonGetter = function(ctor) { + ctor.getInstance = function() { + return ctor.instance_ || (ctor.instance_ = new ctor) + } +}; +goog.typeOf = function(value) { + var s = typeof value; + if(s == "object") { + if(value) { + if(value instanceof Array || !(value instanceof Object) && Object.prototype.toString.call(value) == "[object Array]" || typeof value.length == "number" && typeof value.splice != "undefined" && typeof value.propertyIsEnumerable != "undefined" && !value.propertyIsEnumerable("splice")) { + return"array" + } + if(!(value instanceof Object) && (Object.prototype.toString.call(value) == "[object Function]" || typeof value.call != "undefined" && typeof value.propertyIsEnumerable != "undefined" && !value.propertyIsEnumerable("call"))) { + return"function" + } + }else { + return"null" + } + }else { + if(s == "function" && typeof value.call == "undefined") { + return"object" + } + } + return s +}; +goog.propertyIsEnumerableCustom_ = function(object, propName) { + if(propName in object) { + for(var key in object) { + if(key == propName && Object.prototype.hasOwnProperty.call(object, propName)) { + return true + } + } + } + return false +}; +goog.propertyIsEnumerable_ = function(object, propName) { + return object instanceof Object ? Object.prototype.propertyIsEnumerable.call(object, propName) : goog.propertyIsEnumerableCustom_(object, propName) +}; +goog.isDef = function(val) { + return val !== undefined +}; +goog.isNull = function(val) { + return val === null +}; +goog.isDefAndNotNull = function(val) { + return val != null +}; +goog.isArray = function(val) { + return goog.typeOf(val) == "array" +}; +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + return type == "array" || type == "object" && typeof val.length == "number" +}; +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == "function" +}; +goog.isString = function(val) { + return typeof val == "string" +}; +goog.isBoolean = function(val) { + return typeof val == "boolean" +}; +goog.isNumber = function(val) { + return typeof val == "number" +}; +goog.isFunction = function(val) { + return goog.typeOf(val) == "function" +}; +goog.isObject = function(val) { + var type = goog.typeOf(val); + return type == "object" || type == "array" || type == "function" +}; +goog.getUid = function(obj) { + if(obj.hasOwnProperty && obj.hasOwnProperty(goog.UID_PROPERTY_)) { + return obj[goog.UID_PROPERTY_] + } + obj[goog.UID_PROPERTY_] || (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_); + return obj[goog.UID_PROPERTY_] +}; +goog.removeUid = function(obj) { + "removeAttribute" in obj && obj.removeAttribute(goog.UID_PROPERTY_); + try { + delete obj[goog.UID_PROPERTY_] + }catch(ex) { + } +}; +goog.UID_PROPERTY_ = "closure_uid_" + Math.floor(Math.random() * 2147483648).toString(36); +goog.uidCounter_ = 0; +goog.getHashCode = goog.getUid; +goog.removeHashCode = goog.removeUid; +goog.cloneObject = function(obj) { + var type = goog.typeOf(obj); + if(type == "object" || type == "array") { + if(obj.clone) { + return obj.clone() + } + var clone = type == "array" ? [] : {}; + for(var key in obj) { + clone[key] = goog.cloneObject(obj[key]) + } + return clone + } + return obj +}; +goog.bind = function(fn, selfObj) { + var context = selfObj || goog.global; + if(arguments.length > 2) { + var boundArgs = Array.prototype.slice.call(arguments, 2); + return function() { + var newArgs = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(newArgs, boundArgs); + return fn.apply(context, newArgs) + } + }else { + return function() { + return fn.apply(context, arguments) + } + } +}; +goog.partial = function(fn) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + var newArgs = Array.prototype.slice.call(arguments); + newArgs.unshift.apply(newArgs, args); + return fn.apply(this, newArgs) + } +}; +goog.mixin = function(target, source) { + for(var x in source) { + target[x] = source[x] + } +}; +goog.now = Date.now || function() { + return+new Date +}; +goog.globalEval = function(script) { + if(goog.global.execScript) { + goog.global.execScript(script, "JavaScript") + }else { + if(goog.global.eval) { + if(goog.evalWorksForGlobals_ == null) { + goog.global.eval("var _et_ = 1;"); + if(typeof goog.global._et_ != "undefined") { + delete goog.global._et_; + goog.evalWorksForGlobals_ = true + }else { + goog.evalWorksForGlobals_ = false + } + } + if(goog.evalWorksForGlobals_) { + goog.global.eval(script) + }else { + var doc = goog.global.document, scriptElt = doc.createElement("script"); + scriptElt.type = "text/javascript"; + scriptElt.defer = false; + scriptElt.appendChild(doc.createTextNode(script)); + doc.body.appendChild(scriptElt); + doc.body.removeChild(scriptElt) + } + }else { + throw Error("goog.globalEval not available"); + } + } +}; +goog.typedef = true; +goog.getCssName = function(className, opt_modifier) { + var cssName = className + (opt_modifier ? "-" + opt_modifier : ""); + return goog.cssNameMapping_ && cssName in goog.cssNameMapping_ ? goog.cssNameMapping_[cssName] : cssName +}; +goog.setCssNameMapping = function(mapping) { + goog.cssNameMapping_ = mapping +}; +goog.getMsg = function(str, opt_values) { + var values = opt_values || {}; + for(var key in values) { + var value = ("" + values[key]).replace(/\$/g, "$$$$"); + str = str.replace(RegExp("\\{\\$" + key + "\\}", "gi"), value) + } + return str +}; +goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) { + goog.exportPath_(publicPath, object, opt_objectToExportTo) +}; +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol +}; +goog.inherits = function(childCtor, parentCtor) { + function tempCtor() { + } + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor; + childCtor.prototype.constructor = childCtor +}; +goog.base = function(me, opt_methodName) { + var caller = arguments.callee.caller; + if(caller.superClass_) { + return caller.superClass_.constructor.apply(me, Array.prototype.slice.call(arguments, 1)) + } + var args = Array.prototype.slice.call(arguments, 2), foundCaller = false; + for(var ctor = me.constructor;ctor;ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if(ctor.prototype[opt_methodName] === caller) { + foundCaller = true + }else { + if(foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args) + } + } + } + if(me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args) + }else { + throw Error("goog.base called from a method of one name to a method of a different name"); + } +}; +goog.scope = function(fn) { + fn.call(goog.global) +}; +goog.MODIFY_FUNCTION_PROTOTYPES = true; +if(goog.MODIFY_FUNCTION_PROTOTYPES) { + Function.prototype.bind = function(selfObj) { + if(arguments.length > 1) { + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(this, selfObj); + return goog.bind.apply(null, args) + }else { + return goog.bind(this, selfObj) + } + }; + Function.prototype.partial = function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(this, null); + return goog.bind.apply(null, args) + }; + Function.prototype.inherits = function(parentCtor) { + goog.inherits(this, parentCtor) + }; + Function.prototype.mixin = function(source) { + goog.mixin(this.prototype, source) + } +} +;goog.debug = {}; +goog.debug.Error = function(opt_msg) { + this.stack = Error().stack || ""; + if(opt_msg) { + this.message = String(opt_msg) + } +}; +goog.inherits(goog.debug.Error, Error); +goog.debug.Error.prototype.name = "CustomError";goog.string = {}; +goog.string.Unicode = {NBSP:"\u00a0"}; +goog.string.startsWith = function(str, prefix) { + return str.lastIndexOf(prefix, 0) == 0 +}; +goog.string.endsWith = function(str, suffix) { + var l = str.length - suffix.length; + return l >= 0 && str.indexOf(suffix, l) == l +}; +goog.string.caseInsensitiveStartsWith = function(str, prefix) { + return goog.string.caseInsensitiveCompare(prefix, str.substr(0, prefix.length)) == 0 +}; +goog.string.caseInsensitiveEndsWith = function(str, suffix) { + return goog.string.caseInsensitiveCompare(suffix, str.substr(str.length - suffix.length, suffix.length)) == 0 +}; +goog.string.subs = function(str) { + for(var i = 1;i < arguments.length;i++) { + var replacement = String(arguments[i]).replace(/\$/g, "$$$$"); + str = str.replace(/\%s/, replacement) + } + return str +}; +goog.string.collapseWhitespace = function(str) { + return str.replace(/[\s\xa0]+/g, " ").replace(/^\s+|\s+$/g, "") +}; +goog.string.isEmpty = function(str) { + return/^[\s\xa0]*$/.test(str) +}; +goog.string.isEmptySafe = function(str) { + return goog.string.isEmpty(goog.string.makeSafe(str)) +}; +goog.string.isBreakingWhitespace = function(str) { + return!/[^\t\n\r ]/.test(str) +}; +goog.string.isAlpha = function(str) { + return!/[^a-zA-Z]/.test(str) +}; +goog.string.isNumeric = function(str) { + return!/[^0-9]/.test(str) +}; +goog.string.isAlphaNumeric = function(str) { + return!/[^a-zA-Z0-9]/.test(str) +}; +goog.string.isSpace = function(ch) { + return ch == " " +}; +goog.string.isUnicodeChar = function(ch) { + return ch.length == 1 && ch >= " " && ch <= "~" || ch >= "\u0080" && ch <= "\ufffd" +}; +goog.string.stripNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)+/g, " ") +}; +goog.string.canonicalizeNewlines = function(str) { + return str.replace(/(\r\n|\r|\n)/g, "\n") +}; +goog.string.normalizeWhitespace = function(str) { + return str.replace(/\xa0|\s/g, " ") +}; +goog.string.normalizeSpaces = function(str) { + return str.replace(/\xa0|[ \t]+/g, " ") +}; +goog.string.trim = function(str) { + return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, "") +}; +goog.string.trimLeft = function(str) { + return str.replace(/^[\s\xa0]+/, "") +}; +goog.string.trimRight = function(str) { + return str.replace(/[\s\xa0]+$/, "") +}; +goog.string.caseInsensitiveCompare = function(str1, str2) { + var test1 = String(str1).toLowerCase(), test2 = String(str2).toLowerCase(); + return test1 < test2 ? -1 : test1 == test2 ? 0 : 1 +}; +goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g; +goog.string.numerateCompare = function(str1, str2) { + if(str1 == str2) { + return 0 + } + if(!str1) { + return-1 + } + if(!str2) { + return 1 + } + var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_), tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_), count = Math.min(tokens1.length, tokens2.length); + for(var i = 0;i < count;i++) { + var a = tokens1[i], b = tokens2[i]; + if(a != b) { + var num1 = parseInt(a, 10); + if(!isNaN(num1)) { + var num2 = parseInt(b, 10); + if(!isNaN(num2) && num1 - num2) { + return num1 - num2 + } + } + return a < b ? -1 : 1 + } + } + if(tokens1.length != tokens2.length) { + return tokens1.length - tokens2.length + } + return str1 < str2 ? -1 : 1 +}; +goog.string.encodeUriRegExp_ = /^[a-zA-Z0-9\-_.!~*'()]*$/; +goog.string.urlEncode = function(str) { + str = String(str); + if(!goog.string.encodeUriRegExp_.test(str)) { + return encodeURIComponent(str) + } + return str +}; +goog.string.urlDecode = function(str) { + return decodeURIComponent(str.replace(/\+/g, " ")) +}; +goog.string.newLineToBr = function(str, opt_xml) { + return str.replace(/(\r\n|\r|\n)/g, opt_xml ? "
" : "
") +}; +goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) { + if(opt_isLikelyToContainHtmlChars) { + return str.replace(goog.string.amperRe_, "&").replace(goog.string.ltRe_, "<").replace(goog.string.gtRe_, ">").replace(goog.string.quotRe_, """) + }else { + if(!goog.string.allRe_.test(str)) { + return str + } + if(str.indexOf("&") != -1) { + str = str.replace(goog.string.amperRe_, "&") + } + if(str.indexOf("<") != -1) { + str = str.replace(goog.string.ltRe_, "<") + } + if(str.indexOf(">") != -1) { + str = str.replace(goog.string.gtRe_, ">") + } + if(str.indexOf('"') != -1) { + str = str.replace(goog.string.quotRe_, """) + } + return str + } +}; +goog.string.amperRe_ = /&/g; +goog.string.ltRe_ = /
/g; +goog.string.quotRe_ = /\"/g; +goog.string.allRe_ = /[&<>\"]/; +goog.string.unescapeEntities = function(str) { + if(goog.string.contains(str, "&")) { + return"document" in goog.global && !goog.string.contains(str, "<") ? goog.string.unescapeEntitiesUsingDom_(str) : goog.string.unescapePureXmlEntities_(str) + } + return str +}; +goog.string.unescapeEntitiesUsingDom_ = function(str) { + var el = goog.global.document.createElement("a"); + el.innerHTML = str; + el[goog.string.NORMALIZE_FN_] && el[goog.string.NORMALIZE_FN_](); + str = el.firstChild.nodeValue; + el.innerHTML = ""; + return str +}; +goog.string.unescapePureXmlEntities_ = function(str) { + return str.replace(/&([^;]+);/g, function(s, entity) { + switch(entity) { + case "amp": + return"&"; + case "lt": + return"<"; + case "gt": + return">"; + case "quot": + return'"'; + default: + if(entity.charAt(0) == "#") { + var n = Number("0" + entity.substr(1)); + if(!isNaN(n)) { + return String.fromCharCode(n) + } + } + return s + } + }) +}; +goog.string.NORMALIZE_FN_ = "normalize"; +goog.string.whitespaceEscape = function(str, opt_xml) { + return goog.string.newLineToBr(str.replace(/ /g, "  "), opt_xml) +}; +goog.string.stripQuotes = function(str, quoteChars) { + var length = quoteChars.length; + for(var i = 0;i < length;i++) { + var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i); + if(str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) { + return str.substring(1, str.length - 1) + } + } + return str +}; +goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) { + if(opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str) + } + if(str.length > chars) { + str = str.substring(0, chars - 3) + "..." + } + if(opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str) + } + return str +}; +goog.string.truncateMiddle = function(str, chars, opt_protectEscapedCharacters) { + if(opt_protectEscapedCharacters) { + str = goog.string.unescapeEntities(str) + } + if(str.length > chars) { + var half = Math.floor(chars / 2), endPos = str.length - half; + half += chars % 2; + str = str.substring(0, half) + "..." + str.substring(endPos) + } + if(opt_protectEscapedCharacters) { + str = goog.string.htmlEscape(str) + } + return str +}; +goog.string.specialEscapeChars_ = {"\u0000":"\\0", "\u0008":"\\b", "\u000c":"\\f", "\n":"\\n", "\r":"\\r", "\t":"\\t", "\u000b":"\\x0B", '"':'\\"', "\\":"\\\\"}; +goog.string.jsEscapeCache_ = {"'":"\\'"}; +goog.string.quote = function(s) { + s = String(s); + if(s.quote) { + return s.quote() + }else { + var sb = ['"']; + for(var i = 0;i < s.length;i++) { + var ch = s.charAt(i), cc = ch.charCodeAt(0); + sb[i + 1] = goog.string.specialEscapeChars_[ch] || (cc > 31 && cc < 127 ? ch : goog.string.escapeChar(ch)) + } + sb.push('"'); + return sb.join("") + } +}; +goog.string.escapeString = function(str) { + var sb = []; + for(var i = 0;i < str.length;i++) { + sb[i] = goog.string.escapeChar(str.charAt(i)) + } + return sb.join("") +}; +goog.string.escapeChar = function(c) { + if(c in goog.string.jsEscapeCache_) { + return goog.string.jsEscapeCache_[c] + } + if(c in goog.string.specialEscapeChars_) { + return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c] + } + var rv = c, cc = c.charCodeAt(0); + if(cc > 31 && cc < 127) { + rv = c + }else { + if(cc < 256) { + rv = "\\x"; + if(cc < 16 || cc > 256) { + rv += "0" + } + }else { + rv = "\\u"; + if(cc < 4096) { + rv += "0" + } + } + rv += cc.toString(16).toUpperCase() + } + return goog.string.jsEscapeCache_[c] = rv +}; +goog.string.toMap = function(s) { + var rv = {}; + for(var i = 0;i < s.length;i++) { + rv[s.charAt(i)] = true + } + return rv +}; +goog.string.contains = function(s, ss) { + return s.indexOf(ss) != -1 +}; +goog.string.removeAt = function(s, index, stringLength) { + var resultStr = s; + if(index >= 0 && index < s.length && stringLength > 0) { + resultStr = s.substr(0, index) + s.substr(index + stringLength, s.length - index - stringLength) + } + return resultStr +}; +goog.string.remove = function(s, ss) { + var re = RegExp(goog.string.regExpEscape(ss), ""); + return s.replace(re, "") +}; +goog.string.removeAll = function(s, ss) { + var re = RegExp(goog.string.regExpEscape(ss), "g"); + return s.replace(re, "") +}; +goog.string.regExpEscape = function(s) { + return String(s).replace(/([-()\[\]{}+?*.$\^|,:# right) { + return 1 + } + } + return 0 +}; +goog.string.HASHCODE_MAX_ = 4294967296; +goog.string.hashCode = function(str) { + var result = 0; + for(var i = 0;i < str.length;++i) { + result = 31 * result + str.charCodeAt(i); + result %= goog.string.HASHCODE_MAX_ + } + return result +}; +goog.string.uniqueStringCounter_ = Math.random() * 2147483648 | 0; +goog.string.createUniqueString = function() { + return"goog_" + goog.string.uniqueStringCounter_++ +}; +goog.string.toNumber = function(str) { + var num = Number(str); + if(num == 0 && goog.string.isEmpty(str)) { + return NaN + } + return num +};goog.asserts = {}; +goog.asserts.ENABLE_ASSERTS = goog.DEBUG; +goog.asserts.AssertionError = function(messagePattern, messageArgs) { + messageArgs.unshift(messagePattern); + goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs)); + messageArgs.shift(); + this.messagePattern = messagePattern +}; +goog.inherits(goog.asserts.AssertionError, goog.debug.Error); +goog.asserts.AssertionError.prototype.name = "AssertionError"; +goog.asserts.doAssertFailure_ = function(defaultMessage, defaultArgs, givenMessage, givenArgs) { + var message = "Assertion failed"; + if(givenMessage) { + message += ": " + givenMessage; + var args = givenArgs + }else { + if(defaultMessage) { + message += ": " + defaultMessage; + args = defaultArgs + } + } + throw new goog.asserts.AssertionError("" + message, args || []); +}; +goog.asserts.assert = function(condition, opt_message) { + goog.asserts.ENABLE_ASSERTS && !condition && goog.asserts.doAssertFailure_("", null, opt_message, Array.prototype.slice.call(arguments, 2)) +}; +goog.asserts.fail = function(opt_message) { + if(goog.asserts.ENABLE_ASSERTS) { + throw new goog.asserts.AssertionError("Failure" + (opt_message ? ": " + opt_message : ""), Array.prototype.slice.call(arguments, 1)); + } +}; +goog.asserts.assertNumber = function(value, opt_message) { + goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value) && goog.asserts.doAssertFailure_("Expected number but got %s.", [value], opt_message, Array.prototype.slice.call(arguments, 2)); + return value +}; +goog.asserts.assertString = function(value, opt_message) { + goog.asserts.ENABLE_ASSERTS && !goog.isString(value) && goog.asserts.doAssertFailure_("Expected string but got %s.", [value], opt_message, Array.prototype.slice.call(arguments, 2)); + return value +}; +goog.asserts.assertFunction = function(value, opt_message) { + goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value) && goog.asserts.doAssertFailure_("Expected function but got %s.", [value], opt_message, Array.prototype.slice.call(arguments, 2)); + return value +}; +goog.asserts.assertObject = function(value, opt_message) { + goog.asserts.ENABLE_ASSERTS && !goog.isObject(value) && goog.asserts.doAssertFailure_("Expected object but got %s.", [value], opt_message, Array.prototype.slice.call(arguments, 2)); + return value +}; +goog.asserts.assertArray = function(value, opt_message) { + goog.asserts.ENABLE_ASSERTS && !goog.isArray(value) && goog.asserts.doAssertFailure_("Expected array but got %s.", [value], opt_message, Array.prototype.slice.call(arguments, 2)); + return value +}; +goog.asserts.assertInstanceof = function(value, type, opt_message) { + goog.asserts.ENABLE_ASSERTS && !(value instanceof type) && goog.asserts.doAssertFailure_("instanceof check failed.", null, opt_message, Array.prototype.slice.call(arguments, 3)) +};goog.array = {}; +goog.array.peek = function(array) { + return array[array.length - 1] +}; +goog.array.ARRAY_PROTOTYPE_ = Array.prototype; +goog.array.indexOf = goog.array.ARRAY_PROTOTYPE_.indexOf ? function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex) +} : function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? 0 : opt_fromIndex < 0 ? Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex; + if(goog.isString(arr)) { + if(!goog.isString(obj) || obj.length != 1) { + return-1 + } + return arr.indexOf(obj, fromIndex) + } + for(var i = fromIndex;i < arr.length;i++) { + if(i in arr && arr[i] === obj) { + return i + } + } + return-1 +}; +goog.array.lastIndexOf = goog.array.ARRAY_PROTOTYPE_.lastIndexOf ? function(arr, obj, opt_fromIndex) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex) +} : function(arr, obj, opt_fromIndex) { + var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex; + if(fromIndex < 0) { + fromIndex = Math.max(0, arr.length + fromIndex) + } + if(goog.isString(arr)) { + if(!goog.isString(obj) || obj.length != 1) { + return-1 + } + return arr.lastIndexOf(obj, fromIndex) + } + for(var i = fromIndex;i >= 0;i--) { + if(i in arr && arr[i] === obj) { + return i + } + } + return-1 +}; +goog.array.forEach = goog.array.ARRAY_PROTOTYPE_.forEach ? function(arr, f, opt_obj) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj) +} : function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + i in arr2 && f.call(opt_obj, arr2[i], i, arr) + } +}; +goog.array.forEachRight = function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = l - 1;i >= 0;--i) { + i in arr2 && f.call(opt_obj, arr2[i], i, arr) + } +}; +goog.array.filter = goog.array.ARRAY_PROTOTYPE_.filter ? function(arr, f, opt_obj) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj) +} : function(arr, f, opt_obj) { + var l = arr.length, res = [], resLength = 0, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + if(i in arr2) { + var val = arr2[i]; + if(f.call(opt_obj, val, i, arr)) { + res[resLength++] = val + } + } + } + return res +}; +goog.array.map = goog.array.ARRAY_PROTOTYPE_.map ? function(arr, f, opt_obj) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj) +} : function(arr, f, opt_obj) { + var l = arr.length, res = Array(l), arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + if(i in arr2) { + res[i] = f.call(opt_obj, arr2[i], i, arr) + } + } + return res +}; +goog.array.reduce = function(arr, f, val$$0, opt_obj) { + if(arr.reduce) { + return opt_obj ? arr.reduce(goog.bind(f, opt_obj), val$$0) : arr.reduce(f, val$$0) + } + var rval = val$$0; + goog.array.forEach(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr) + }); + return rval +}; +goog.array.reduceRight = function(arr, f, val$$0, opt_obj) { + if(arr.reduceRight) { + return opt_obj ? arr.reduceRight(goog.bind(f, opt_obj), val$$0) : arr.reduceRight(f, val$$0) + } + var rval = val$$0; + goog.array.forEachRight(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr) + }); + return rval +}; +goog.array.some = goog.array.ARRAY_PROTOTYPE_.some ? function(arr, f, opt_obj) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj) +} : function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return true + } + } + return false +}; +goog.array.every = goog.array.ARRAY_PROTOTYPE_.every ? function(arr, f, opt_obj) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj) +} : function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + if(i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) { + return false + } + } + return true +}; +goog.array.find = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i] +}; +goog.array.findIndex = function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = 0;i < l;i++) { + if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return i + } + } + return-1 +}; +goog.array.findRight = function(arr, f, opt_obj) { + var i = goog.array.findIndexRight(arr, f, opt_obj); + return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i] +}; +goog.array.findIndexRight = function(arr, f, opt_obj) { + var l = arr.length, arr2 = goog.isString(arr) ? arr.split("") : arr; + for(var i = l - 1;i >= 0;i--) { + if(i in arr2 && f.call(opt_obj, arr2[i], i, arr)) { + return i + } + } + return-1 +}; +goog.array.contains = function(arr, obj) { + return goog.array.indexOf(arr, obj) >= 0 +}; +goog.array.isEmpty = function(arr) { + return arr.length == 0 +}; +goog.array.clear = function(arr) { + if(!goog.isArray(arr)) { + for(var i = arr.length - 1;i >= 0;i--) { + delete arr[i] + } + } + arr.length = 0 +}; +goog.array.insert = function(arr, obj) { + goog.array.contains(arr, obj) || arr.push(obj) +}; +goog.array.insertAt = function(arr, obj, opt_i) { + goog.array.splice(arr, opt_i, 0, obj) +}; +goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) { + goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd) +}; +goog.array.insertBefore = function(arr, obj, opt_obj2) { + var i; + arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0 ? arr.push(obj) : goog.array.insertAt(arr, obj, i) +}; +goog.array.remove = function(arr, obj) { + var i = goog.array.indexOf(arr, obj), rv; + if(rv = i >= 0) { + goog.array.removeAt(arr, i) + } + return rv +}; +goog.array.removeAt = function(arr, i) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1 +}; +goog.array.removeIf = function(arr, f, opt_obj) { + var i = goog.array.findIndex(arr, f, opt_obj); + if(i >= 0) { + goog.array.removeAt(arr, i); + return true + } + return false +}; +goog.array.concat = function() { + return goog.array.ARRAY_PROTOTYPE_.concat.apply(goog.array.ARRAY_PROTOTYPE_, arguments) +}; +goog.array.clone = function(arr) { + if(goog.isArray(arr)) { + return goog.array.concat(arr) + }else { + var rv = [], i = 0; + for(var len = arr.length;i < len;i++) { + rv[i] = arr[i] + } + return rv + } +}; +goog.array.toArray = function(object) { + if(goog.isArray(object)) { + return goog.array.concat(object) + } + return goog.array.clone(object) +}; +goog.array.extend = function(arr1) { + for(var i = 1;i < arguments.length;i++) { + var arr2 = arguments[i], isArrayLike; + if(goog.isArray(arr2) || (isArrayLike = goog.isArrayLike(arr2)) && arr2.hasOwnProperty("callee")) { + arr1.push.apply(arr1, arr2) + }else { + if(isArrayLike) { + var len1 = arr1.length, len2 = arr2.length; + for(var j = 0;j < len2;j++) { + arr1[len1 + j] = arr2[j] + } + }else { + arr1.push(arr2) + } + } + } +}; +goog.array.splice = function(arr) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return goog.array.ARRAY_PROTOTYPE_.splice.apply(arr, goog.array.slice(arguments, 1)) +}; +goog.array.slice = function(arr, start, opt_end) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + return arguments.length <= 2 ? goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start) : goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end) +}; +goog.array.removeDuplicates = function(arr, opt_rv) { + var rv = opt_rv || arr, seen = {}, cursorInsert = 0; + for(var cursorRead = 0;cursorRead < arr.length;) { + var current = arr[cursorRead++], uid = goog.isObject(current) ? goog.getUid(current) : current; + if(!Object.prototype.hasOwnProperty.call(seen, uid)) { + seen[uid] = true; + rv[cursorInsert++] = current + } + } + rv.length = cursorInsert +}; +goog.array.binarySearch = function(arr, target, opt_compareFn) { + var left = 0, right = arr.length; + for(var found;left < right;) { + var middle = left + right >> 1, compareResult, currentValue = arr[middle]; + if(opt_compareFn) { + compareResult = opt_compareFn(target, currentValue) + }else { + var comparableTarget = target; + compareResult = comparableTarget > currentValue ? 1 : comparableTarget < currentValue ? -1 : 0 + } + if(compareResult > 0) { + left = middle + 1 + }else { + right = middle; + found = !compareResult + } + } + return found ? left : ~left +}; +goog.array.binarySelect = function(arr, evaluator, opt_obj) { + var left = 0, right = arr.length; + for(var found;left < right;) { + var middle = left + right >> 1, evalResult = evaluator.call(opt_obj, arr[middle], middle, arr); + if(evalResult > 0) { + left = middle + 1 + }else { + right = middle; + found = !evalResult + } + } + return found ? left : ~left +}; +goog.array.sort = function(arr, opt_compareFn) { + goog.asserts.assert(arr || goog.isString(arr)); + goog.asserts.assertNumber(arr.length); + goog.array.ARRAY_PROTOTYPE_.sort.call(arr, opt_compareFn || goog.array.defaultCompare) +}; +goog.array.stableSort = function(arr, opt_compareFn) { + function stableCompareFn(obj1, obj2) { + return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index + } + for(var i = 0;i < arr.length;i++) { + arr[i] = {index:i, value:arr[i]} + } + var valueCompareFn = opt_compareFn || goog.array.defaultCompare; + goog.array.sort(arr, stableCompareFn); + for(i = 0;i < arr.length;i++) { + arr[i] = arr[i].value + } +}; +goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) { + var compare = opt_compareFn || goog.array.defaultCompare; + goog.array.sort(arr, function(a, b) { + return compare(a[key], b[key]) + }) +}; +goog.array.equals = function(arr1, arr2, opt_equalsFn) { + if(!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) || arr1.length != arr2.length) { + return false + } + var l = arr1.length, equalsFn = opt_equalsFn || goog.array.defaultCompareEquality; + for(var i = 0;i < l;i++) { + if(!equalsFn(arr1[i], arr2[i])) { + return false + } + } + return true +}; +goog.array.compare = function(arr1, arr2, opt_equalsFn) { + return goog.array.equals(arr1, arr2, opt_equalsFn) +}; +goog.array.defaultCompare = function(a, b) { + return a > b ? 1 : a < b ? -1 : 0 +}; +goog.array.defaultCompareEquality = function(a, b) { + return a === b +}; +goog.array.binaryInsert = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + if(index < 0) { + goog.array.insertAt(array, value, -(index + 1)); + return true + } + return false +}; +goog.array.binaryRemove = function(array, value, opt_compareFn) { + var index = goog.array.binarySearch(array, value, opt_compareFn); + return index >= 0 ? goog.array.removeAt(array, index) : false +}; +goog.array.bucket = function(array, sorter) { + var buckets = {}; + for(var i = 0;i < array.length;i++) { + var value = array[i], key = sorter(value, i, array); + if(goog.isDef(key)) { + var bucket = buckets[key] || (buckets[key] = []); + bucket.push(value) + } + } + return buckets +}; +goog.array.repeat = function(value, n) { + var array = []; + for(var i = 0;i < n;i++) { + array[i] = value + } + return array +}; +goog.array.flatten = function() { + var result = []; + for(var i = 0;i < arguments.length;i++) { + var element = arguments[i]; + goog.isArray(element) ? result.push.apply(result, goog.array.flatten.apply(null, element)) : result.push(element) + } + return result +}; +goog.array.rotate = function(array, n) { + goog.asserts.assert(array || goog.isString(array)); + goog.asserts.assertNumber(array.length); + if(array.length) { + n %= array.length; + if(n > 0) { + goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n)) + }else { + n < 0 && goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n)) + } + } + return array +}; +goog.array.zip = function() { + if(!arguments.length) { + return[] + } + var result = []; + for(var i = 0;;i++) { + var value = []; + for(var j = 0;j < arguments.length;j++) { + var arr = arguments[j]; + if(i >= arr.length) { + return result + } + value.push(arr[i]) + } + result.push(value) + } +};goog.math = {}; +goog.math.Coordinate = function(opt_x, opt_y) { + this.x = goog.isDef(opt_x) ? opt_x : 0; + this.y = goog.isDef(opt_y) ? opt_y : 0 +}; +goog.math.Coordinate.prototype.clone = function() { + return new goog.math.Coordinate(this.x, this.y) +}; +if(goog.DEBUG) { + goog.math.Coordinate.prototype.toString = function() { + return"(" + this.x + ", " + this.y + ")" + } +} +goog.math.Coordinate.equals = function(a, b) { + if(a == b) { + return true + } + if(!a || !b) { + return false + } + return a.x == b.x && a.y == b.y +}; +goog.math.Coordinate.distance = function(a, b) { + var dx = a.x - b.x, dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy) +}; +goog.math.Coordinate.squaredDistance = function(a, b) { + var dx = a.x - b.x, dy = a.y - b.y; + return dx * dx + dy * dy +}; +goog.math.Coordinate.difference = function(a, b) { + return new goog.math.Coordinate(a.x - b.x, a.y - b.y) +}; +goog.math.Coordinate.sum = function(a, b) { + return new goog.math.Coordinate(a.x + b.x, a.y + b.y) +};goog.math.Size = function(width, height) { + this.width = width; + this.height = height +}; +goog.math.Size.equals = function(a, b) { + if(a == b) { + return true + } + if(!a || !b) { + return false + } + return a.width == b.width && a.height == b.height +}; +goog.math.Size.prototype.clone = function() { + return new goog.math.Size(this.width, this.height) +}; +if(goog.DEBUG) { + goog.math.Size.prototype.toString = function() { + return"(" + this.width + " x " + this.height + ")" + } +} +a = goog.math.Size.prototype; +a.area = function() { + return this.width * this.height +}; +a.isEmpty = function() { + return!this.area() +}; +a.ceil = function() { + this.width = Math.ceil(this.width); + this.height = Math.ceil(this.height); + return this +}; +a.floor = function() { + this.width = Math.floor(this.width); + this.height = Math.floor(this.height); + return this +}; +a.round = function() { + this.width = Math.round(this.width); + this.height = Math.round(this.height); + return this +}; +a.scale = function(s) { + this.width *= s; + this.height *= s; + return this +};goog.object = {}; +goog.object.forEach = function(obj, f, opt_obj) { + for(var key in obj) { + f.call(opt_obj, obj[key], key, obj) + } +}; +goog.object.filter = function(obj, f, opt_obj) { + var res = {}; + for(var key in obj) { + if(f.call(opt_obj, obj[key], key, obj)) { + res[key] = obj[key] + } + } + return res +}; +goog.object.map = function(obj, f, opt_obj) { + var res = {}; + for(var key in obj) { + res[key] = f.call(opt_obj, obj[key], key, obj) + } + return res +}; +goog.object.some = function(obj, f, opt_obj) { + for(var key in obj) { + if(f.call(opt_obj, obj[key], key, obj)) { + return true + } + } + return false +}; +goog.object.every = function(obj, f, opt_obj) { + for(var key in obj) { + if(!f.call(opt_obj, obj[key], key, obj)) { + return false + } + } + return true +}; +goog.object.getCount = function(obj) { + var rv = 0; + for(var key in obj) { + rv++ + } + return rv +}; +goog.object.getAnyKey = function(obj) { + for(var key in obj) { + return key + } +}; +goog.object.getAnyValue = function(obj) { + for(var key in obj) { + return obj[key] + } +}; +goog.object.contains = function(obj, val) { + return goog.object.containsValue(obj, val) +}; +goog.object.getValues = function(obj) { + var res = [], i = 0; + for(var key in obj) { + res[i++] = obj[key] + } + return res +}; +goog.object.getKeys = function(obj) { + var res = [], i = 0; + for(var key in obj) { + res[i++] = key + } + return res +}; +goog.object.containsKey = function(obj, key) { + return key in obj +}; +goog.object.containsValue = function(obj, val) { + for(var key in obj) { + if(obj[key] == val) { + return true + } + } + return false +}; +goog.object.findKey = function(obj, f, opt_this) { + for(var key in obj) { + if(f.call(opt_this, obj[key], key, obj)) { + return key + } + } +}; +goog.object.findValue = function(obj, f, opt_this) { + var key = goog.object.findKey(obj, f, opt_this); + return key && obj[key] +}; +goog.object.isEmpty = function(obj) { + for(var key in obj) { + return false + } + return true +}; +goog.object.clear = function(obj) { + var keys = goog.object.getKeys(obj); + for(var i = keys.length - 1;i >= 0;i--) { + goog.object.remove(obj, keys[i]) + } +}; +goog.object.remove = function(obj, key) { + var rv; + if(rv = key in obj) { + delete obj[key] + } + return rv +}; +goog.object.add = function(obj, key, val) { + if(key in obj) { + throw Error('The object already contains the key "' + key + '"'); + } + goog.object.set(obj, key, val) +}; +goog.object.get = function(obj, key, opt_val) { + if(key in obj) { + return obj[key] + } + return opt_val +}; +goog.object.set = function(obj, key, value) { + obj[key] = value +}; +goog.object.setIfUndefined = function(obj, key, value) { + return key in obj ? obj[key] : obj[key] = value +}; +goog.object.clone = function(obj) { + var res = {}; + for(var key in obj) { + res[key] = obj[key] + } + return res +}; +goog.object.transpose = function(obj) { + var transposed = {}; + for(var key in obj) { + transposed[obj[key]] = key + } + return transposed +}; +goog.object.PROTOTYPE_FIELDS_ = ["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"]; +goog.object.extend = function(target) { + var key, source; + for(var i = 1;i < arguments.length;i++) { + source = arguments[i]; + for(key in source) { + target[key] = source[key] + } + for(var j = 0;j < goog.object.PROTOTYPE_FIELDS_.length;j++) { + key = goog.object.PROTOTYPE_FIELDS_[j]; + if(Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } +}; +goog.object.create = function() { + var argLength = arguments.length; + if(argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.create.apply(null, arguments[0]) + } + if(argLength % 2) { + throw Error("Uneven number of arguments"); + } + var rv = {}; + for(var i = 0;i < argLength;i += 2) { + rv[arguments[i]] = arguments[i + 1] + } + return rv +}; +goog.object.createSet = function() { + var argLength = arguments.length; + if(argLength == 1 && goog.isArray(arguments[0])) { + return goog.object.createSet.apply(null, arguments[0]) + } + var rv = {}; + for(var i = 0;i < argLength;i++) { + rv[arguments[i]] = true + } + return rv +};goog.userAgent = {}; +goog.userAgent.ASSUME_IE = false; +goog.userAgent.ASSUME_GECKO = false; +goog.userAgent.ASSUME_WEBKIT = false; +goog.userAgent.ASSUME_MOBILE_WEBKIT = false; +goog.userAgent.ASSUME_OPERA = false; +goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE || goog.userAgent.ASSUME_GECKO || goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_OPERA; +goog.userAgent.getUserAgentString = function() { + return goog.global.navigator ? goog.global.navigator.userAgent : null +}; +goog.userAgent.getNavigator = function() { + return goog.global.navigator +}; +goog.userAgent.init_ = function() { + goog.userAgent.detectedOpera_ = false; + goog.userAgent.detectedIe_ = false; + goog.userAgent.detectedWebkit_ = false; + goog.userAgent.detectedMobile_ = false; + goog.userAgent.detectedGecko_ = false; + var ua; + if(!goog.userAgent.BROWSER_KNOWN_ && (ua = goog.userAgent.getUserAgentString())) { + var navigator = goog.userAgent.getNavigator(); + goog.userAgent.detectedOpera_ = ua.indexOf("Opera") == 0; + goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ && ua.indexOf("MSIE") != -1; + goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ && ua.indexOf("WebKit") != -1; + goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ && ua.indexOf("Mobile") != -1; + goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ && !goog.userAgent.detectedWebkit_ && navigator.product == "Gecko" + } +}; +goog.userAgent.BROWSER_KNOWN_ || goog.userAgent.init_(); +goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_; +goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_; +goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_GECKO : goog.userAgent.detectedGecko_; +goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ? goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT : goog.userAgent.detectedWebkit_; +goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.detectedMobile_; +goog.userAgent.SAFARI = goog.userAgent.WEBKIT; +goog.userAgent.determinePlatform_ = function() { + var navigator = goog.userAgent.getNavigator(); + return navigator && navigator.platform || "" +}; +goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_(); +goog.userAgent.ASSUME_MAC = false; +goog.userAgent.ASSUME_WINDOWS = false; +goog.userAgent.ASSUME_LINUX = false; +goog.userAgent.ASSUME_X11 = false; +goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC || goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX || goog.userAgent.ASSUME_X11; +goog.userAgent.initPlatform_ = function() { + goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM, "Mac"); + goog.userAgent.detectedWindows_ = goog.string.contains(goog.userAgent.PLATFORM, "Win"); + goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM, "Linux"); + goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() && goog.string.contains(goog.userAgent.getNavigator().appVersion || "", "X11") +}; +goog.userAgent.PLATFORM_KNOWN_ || goog.userAgent.initPlatform_(); +goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_; +goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_; +goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_; +goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ? goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_; +goog.userAgent.determineVersion_ = function() { + var version = "", re; + if(goog.userAgent.OPERA && goog.global.opera) { + var operaVersion = goog.global.opera.version; + version = typeof operaVersion == "function" ? operaVersion() : operaVersion + }else { + if(goog.userAgent.GECKO) { + re = /rv\:([^\);]+)(\)|;)/ + }else { + if(goog.userAgent.IE) { + re = /MSIE\s+([^\);]+)(\)|;)/ + }else { + if(goog.userAgent.WEBKIT) { + re = /WebKit\/(\S+)/ + } + } + } + if(re) { + var arr = re.exec(goog.userAgent.getUserAgentString()); + version = arr ? arr[1] : "" + } + } + return version +}; +goog.userAgent.VERSION = goog.userAgent.determineVersion_(); +goog.userAgent.compare = function(v1, v2) { + return goog.string.compareVersions(v1, v2) +}; +goog.userAgent.isVersionCache_ = {}; +goog.userAgent.isVersion = function(version) { + return goog.userAgent.isVersionCache_[version] || (goog.userAgent.isVersionCache_[version] = goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0) +};goog.dom = {}; +goog.dom.classes = {}; +goog.dom.classes.set = function(element, className) { + element.className = className +}; +goog.dom.classes.get = function(element) { + var className = element.className; + return className && typeof className.split == "function" ? className.split(/\s+/) : [] +}; +goog.dom.classes.add = function(element) { + var classes = goog.dom.classes.get(element), args = goog.array.slice(arguments, 1), b = goog.dom.classes.add_(classes, args); + element.className = classes.join(" "); + return b +}; +goog.dom.classes.remove = function(element) { + var classes = goog.dom.classes.get(element), args = goog.array.slice(arguments, 1), b = goog.dom.classes.remove_(classes, args); + element.className = classes.join(" "); + return b +}; +goog.dom.classes.add_ = function(classes, args) { + var rv = 0; + for(var i = 0;i < args.length;i++) { + if(!goog.array.contains(classes, args[i])) { + classes.push(args[i]); + rv++ + } + } + return rv == args.length +}; +goog.dom.classes.remove_ = function(classes, args) { + var rv = 0; + for(var i = 0;i < classes.length;i++) { + if(goog.array.contains(args, classes[i])) { + goog.array.splice(classes, i--, 1); + rv++ + } + } + return rv == args.length +}; +goog.dom.classes.swap = function(element, fromClass, toClass) { + var classes = goog.dom.classes.get(element), removed = false; + for(var i = 0;i < classes.length;i++) { + if(classes[i] == fromClass) { + goog.array.splice(classes, i--, 1); + removed = true + } + } + if(removed) { + classes.push(toClass); + element.className = classes.join(" ") + } + return removed +}; +goog.dom.classes.addRemove = function(element, classesToRemove, classesToAdd) { + var classes = goog.dom.classes.get(element); + if(goog.isString(classesToRemove)) { + goog.array.remove(classes, classesToRemove) + }else { + goog.isArray(classesToRemove) && goog.dom.classes.remove_(classes, classesToRemove) + } + if(goog.isString(classesToAdd) && !goog.array.contains(classes, classesToAdd)) { + classes.push(classesToAdd) + }else { + goog.isArray(classesToAdd) && goog.dom.classes.add_(classes, classesToAdd) + } + element.className = classes.join(" ") +}; +goog.dom.classes.has = function(element, className) { + return goog.array.contains(goog.dom.classes.get(element), className) +}; +goog.dom.classes.enable = function(element, className, enabled) { + enabled ? goog.dom.classes.add(element, className) : goog.dom.classes.remove(element, className) +}; +goog.dom.classes.toggle = function(element, className) { + var add = !goog.dom.classes.has(element, className); + goog.dom.classes.enable(element, className, add); + return add +};goog.dom.TagName = {A:"A", ABBR:"ABBR", ACRONYM:"ACRONYM", ADDRESS:"ADDRESS", APPLET:"APPLET", AREA:"AREA", B:"B", BASE:"BASE", BASEFONT:"BASEFONT", BDO:"BDO", BIG:"BIG", BLOCKQUOTE:"BLOCKQUOTE", BODY:"BODY", BR:"BR", BUTTON:"BUTTON", CAPTION:"CAPTION", CENTER:"CENTER", CITE:"CITE", CODE:"CODE", COL:"COL", COLGROUP:"COLGROUP", DD:"DD", DEL:"DEL", DFN:"DFN", DIR:"DIR", DIV:"DIV", DL:"DL", DT:"DT", EM:"EM", FIELDSET:"FIELDSET", FONT:"FONT", FORM:"FORM", FRAME:"FRAME", FRAMESET:"FRAMESET", H1:"H1", +H2:"H2", H3:"H3", H4:"H4", H5:"H5", H6:"H6", HEAD:"HEAD", HR:"HR", HTML:"HTML", I:"I", IFRAME:"IFRAME", IMG:"IMG", INPUT:"INPUT", INS:"INS", ISINDEX:"ISINDEX", KBD:"KBD", LABEL:"LABEL", LEGEND:"LEGEND", LI:"LI", LINK:"LINK", MAP:"MAP", MENU:"MENU", META:"META", NOFRAMES:"NOFRAMES", NOSCRIPT:"NOSCRIPT", OBJECT:"OBJECT", OL:"OL", OPTGROUP:"OPTGROUP", OPTION:"OPTION", P:"P", PARAM:"PARAM", PRE:"PRE", Q:"Q", S:"S", SAMP:"SAMP", SCRIPT:"SCRIPT", SELECT:"SELECT", SMALL:"SMALL", SPAN:"SPAN", STRIKE:"STRIKE", +STRONG:"STRONG", STYLE:"STYLE", SUB:"SUB", SUP:"SUP", TABLE:"TABLE", TBODY:"TBODY", TD:"TD", TEXTAREA:"TEXTAREA", TFOOT:"TFOOT", TH:"TH", THEAD:"THEAD", TITLE:"TITLE", TR:"TR", TT:"TT", U:"U", UL:"UL", VAR:"VAR"};goog.dom.ASSUME_QUIRKS_MODE = false; +goog.dom.ASSUME_STANDARDS_MODE = false; +goog.dom.COMPAT_MODE_KNOWN_ = goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE; +goog.dom.NodeType = {ELEMENT:1, ATTRIBUTE:2, TEXT:3, CDATA_SECTION:4, ENTITY_REFERENCE:5, ENTITY:6, PROCESSING_INSTRUCTION:7, COMMENT:8, DOCUMENT:9, DOCUMENT_TYPE:10, DOCUMENT_FRAGMENT:11, NOTATION:12}; +goog.dom.getDomHelper = function(opt_element) { + return opt_element ? new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) : goog.dom.defaultDomHelper_ || (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper) +}; +goog.dom.getDocument = function() { + return document +}; +goog.dom.getElement = function(element) { + return goog.isString(element) ? document.getElementById(element) : element +}; +goog.dom.$ = goog.dom.getElement; +goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class, opt_el) +}; +goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class, opt_el) { + var parent = opt_el || doc, tagName = opt_tag && opt_tag != "*" ? opt_tag.toUpperCase() : ""; + if(parent.querySelectorAll && (tagName || opt_class) && (!goog.userAgent.WEBKIT || goog.dom.isCss1CompatMode_(doc) || goog.userAgent.isVersion("528"))) { + var query = tagName + (opt_class ? "." + opt_class : ""); + return parent.querySelectorAll(query) + } + if(opt_class && parent.getElementsByClassName) { + var els = parent.getElementsByClassName(opt_class); + if(tagName) { + var arrayLike = {}, len = 0, i = 0; + for(var el;el = els[i];i++) { + if(tagName == el.nodeName) { + arrayLike[len++] = el + } + } + arrayLike.length = len; + return arrayLike + }else { + return els + } + } + els = parent.getElementsByTagName(tagName || "*"); + if(opt_class) { + arrayLike = {}; + len = 0; + for(i = 0;el = els[i];i++) { + var className = el.className; + if(typeof className.split == "function" && goog.array.contains(className.split(/\s+/), opt_class)) { + arrayLike[len++] = el + } + } + arrayLike.length = len; + return arrayLike + }else { + return els + } +}; +goog.dom.$$ = goog.dom.getElementsByTagNameAndClass; +goog.dom.setProperties = function(element, properties) { + goog.object.forEach(properties, function(val, key) { + if(key == "style") { + element.style.cssText = val + }else { + if(key == "class") { + element.className = val + }else { + if(key == "for") { + element.htmlFor = val + }else { + if(key in goog.dom.DIRECT_ATTRIBUTE_MAP_) { + element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val) + }else { + element[key] = val + } + } + } + } + }) +}; +goog.dom.DIRECT_ATTRIBUTE_MAP_ = {cellpadding:"cellPadding", cellspacing:"cellSpacing", colspan:"colSpan", rowspan:"rowSpan", valign:"vAlign", height:"height", width:"width", usemap:"useMap", frameborder:"frameBorder", type:"type"}; +goog.dom.getViewportSize = function(opt_window) { + return goog.dom.getViewportSize_(opt_window || window) +}; +goog.dom.getViewportSize_ = function(win) { + var doc = win.document; + if(goog.userAgent.WEBKIT && !goog.userAgent.isVersion("500") && !goog.userAgent.MOBILE) { + if(typeof win.innerHeight == "undefined") { + win = window + } + var innerHeight = win.innerHeight, scrollHeight = win.document.documentElement.scrollHeight; + if(win == win.top) { + if(scrollHeight < innerHeight) { + innerHeight -= 15 + } + } + return new goog.math.Size(win.innerWidth, innerHeight) + } + var readsFromDocumentElement = goog.dom.isCss1CompatMode_(doc); + if(goog.userAgent.OPERA && !goog.userAgent.isVersion("9.50")) { + readsFromDocumentElement = false + } + var el = readsFromDocumentElement ? doc.documentElement : doc.body; + return new goog.math.Size(el.clientWidth, el.clientHeight) +}; +goog.dom.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(window) +}; +goog.dom.getDocumentHeight_ = function(win) { + var doc = win.document, height = 0; + if(doc) { + var vh = goog.dom.getViewportSize_(win).height, body = doc.body, docEl = doc.documentElement; + if(goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) { + height = docEl.scrollHeight != vh ? docEl.scrollHeight : docEl.offsetHeight + }else { + var sh = docEl.scrollHeight, oh = docEl.offsetHeight; + if(docEl.clientHeight != oh) { + sh = body.scrollHeight; + oh = body.offsetHeight + } + height = sh > vh ? sh > oh ? sh : oh : sh < oh ? sh : oh + } + } + return height +}; +goog.dom.getPageScroll = function(opt_window) { + var win = opt_window || goog.global || window; + return goog.dom.getDomHelper(win.document).getDocumentScroll() +}; +goog.dom.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(document) +}; +goog.dom.getDocumentScroll_ = function(doc) { + var el = goog.dom.getDocumentScrollElement_(doc); + return new goog.math.Coordinate(el.scrollLeft, el.scrollTop) +}; +goog.dom.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(document) +}; +goog.dom.getDocumentScrollElement_ = function(doc) { + return!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body +}; +goog.dom.getWindow = function(opt_doc) { + return opt_doc ? goog.dom.getWindow_(opt_doc) : window +}; +goog.dom.getWindow_ = function(doc) { + return doc.parentWindow || doc.defaultView +}; +goog.dom.createDom = function() { + return goog.dom.createDom_(document, arguments) +}; +goog.dom.createDom_ = function(doc, args) { + var tagName = args[0], attributes = args[1]; + if(goog.userAgent.IE && attributes && (attributes.name || attributes.type)) { + var tagNameArr = ["<", tagName]; + attributes.name && tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name), '"'); + if(attributes.type) { + tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type), '"'); + var clone = {}; + goog.object.extend(clone, attributes); + attributes = clone; + delete attributes.type + } + tagNameArr.push(">"); + tagName = tagNameArr.join("") + } + var element = doc.createElement(tagName); + if(attributes) { + if(goog.isString(attributes)) { + element.className = attributes + }else { + goog.dom.setProperties(element, attributes) + } + } + args.length > 2 && goog.dom.append_(doc, element, args, 2); + return element +}; +goog.dom.append_ = function(doc, parent, args, startIndex) { + function childHandler(child) { + if(child) { + parent.appendChild(goog.isString(child) ? doc.createTextNode(child) : child) + } + } + for(var i = startIndex;i < args.length;i++) { + var arg = args[i]; + goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg) ? goog.array.forEach(goog.dom.isNodeList(arg) ? goog.array.clone(arg) : arg, childHandler) : childHandler(arg) + } +}; +goog.dom.$dom = goog.dom.createDom; +goog.dom.createElement = function(name) { + return document.createElement(name) +}; +goog.dom.createTextNode = function(content) { + return document.createTextNode(content) +}; +goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) { + return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp) +}; +goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) { + var rowHtml = [""]; + for(var i = 0;i < columns;i++) { + rowHtml.push(fillWithNbsp ? " " : "") + } + rowHtml.push(""); + rowHtml = rowHtml.join(""); + var totalHtml = [""]; + for(i = 0;i < rows;i++) { + totalHtml.push(rowHtml) + } + totalHtml.push("
"); + var elem = doc.createElement(goog.dom.TagName.DIV); + elem.innerHTML = totalHtml.join(""); + return elem.removeChild(elem.firstChild) +}; +goog.dom.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(document, htmlString) +}; +goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) { + var tempDiv = doc.createElement("div"); + tempDiv.innerHTML = htmlString; + if(tempDiv.childNodes.length == 1) { + return tempDiv.removeChild(tempDiv.firstChild) + }else { + for(var fragment = doc.createDocumentFragment();tempDiv.firstChild;) { + fragment.appendChild(tempDiv.firstChild) + } + return fragment + } +}; +goog.dom.getCompatMode = function() { + return goog.dom.isCss1CompatMode() ? "CSS1Compat" : "BackCompat" +}; +goog.dom.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(document) +}; +goog.dom.isCss1CompatMode_ = function(doc) { + if(goog.dom.COMPAT_MODE_KNOWN_) { + return goog.dom.ASSUME_STANDARDS_MODE + } + return doc.compatMode == "CSS1Compat" +}; +goog.dom.canHaveChildren = function(node) { + if(node.nodeType != goog.dom.NodeType.ELEMENT) { + return false + } + if("canHaveChildren" in node) { + return node.canHaveChildren + } + switch(node.tagName) { + case goog.dom.TagName.APPLET: + ; + case goog.dom.TagName.AREA: + ; + case goog.dom.TagName.BASE: + ; + case goog.dom.TagName.BR: + ; + case goog.dom.TagName.COL: + ; + case goog.dom.TagName.FRAME: + ; + case goog.dom.TagName.HR: + ; + case goog.dom.TagName.IMG: + ; + case goog.dom.TagName.INPUT: + ; + case goog.dom.TagName.IFRAME: + ; + case goog.dom.TagName.ISINDEX: + ; + case goog.dom.TagName.LINK: + ; + case goog.dom.TagName.NOFRAMES: + ; + case goog.dom.TagName.NOSCRIPT: + ; + case goog.dom.TagName.META: + ; + case goog.dom.TagName.OBJECT: + ; + case goog.dom.TagName.PARAM: + ; + case goog.dom.TagName.SCRIPT: + ; + case goog.dom.TagName.STYLE: + return false + } + return true +}; +goog.dom.appendChild = function(parent, child) { + parent.appendChild(child) +}; +goog.dom.append = function(parent) { + goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1) +}; +goog.dom.removeChildren = function(node) { + for(var child;child = node.firstChild;) { + node.removeChild(child) + } +}; +goog.dom.insertSiblingBefore = function(newNode, refNode) { + refNode.parentNode && refNode.parentNode.insertBefore(newNode, refNode) +}; +goog.dom.insertSiblingAfter = function(newNode, refNode) { + refNode.parentNode && refNode.parentNode.insertBefore(newNode, refNode.nextSibling) +}; +goog.dom.removeNode = function(node) { + return node && node.parentNode ? node.parentNode.removeChild(node) : null +}; +goog.dom.replaceNode = function(newNode, oldNode) { + var parent = oldNode.parentNode; + parent && parent.replaceChild(newNode, oldNode) +}; +goog.dom.flattenElement = function(element) { + var child, parent = element.parentNode; + if(parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) { + if(element.removeNode) { + return element.removeNode(false) + }else { + for(;child = element.firstChild;) { + parent.insertBefore(child, element) + } + return goog.dom.removeNode(element) + } + } +}; +goog.dom.getFirstElementChild = function(node) { + return goog.dom.getNextElementNode_(node.firstChild, true) +}; +goog.dom.getLastElementChild = function(node) { + return goog.dom.getNextElementNode_(node.lastChild, false) +}; +goog.dom.getNextElementSibling = function(node) { + return goog.dom.getNextElementNode_(node.nextSibling, true) +}; +goog.dom.getPreviousElementSibling = function(node) { + return goog.dom.getNextElementNode_(node.previousSibling, false) +}; +goog.dom.getNextElementNode_ = function(node, forward) { + for(;node && node.nodeType != goog.dom.NodeType.ELEMENT;) { + node = forward ? node.nextSibling : node.previousSibling + } + return node +}; +goog.dom.getNextNode = function(node) { + if(!node) { + return null + } + if(node.firstChild) { + return node.firstChild + } + for(;node && !node.nextSibling;) { + node = node.parentNode + } + return node ? node.nextSibling : null +}; +goog.dom.getPreviousNode = function(node) { + if(!node) { + return null + } + if(!node.previousSibling) { + return node.parentNode + } + for(node = node.previousSibling;node && node.lastChild;) { + node = node.lastChild + } + return node +}; +goog.dom.isNodeLike = function(obj) { + return goog.isObject(obj) && obj.nodeType > 0 +}; +goog.dom.contains = function(parent, descendant) { + if(parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) { + return parent == descendant || parent.contains(descendant) + } + if(typeof parent.compareDocumentPosition != "undefined") { + return parent == descendant || Boolean(parent.compareDocumentPosition(descendant) & 16) + } + for(;descendant && parent != descendant;) { + descendant = descendant.parentNode + } + return descendant == parent +}; +goog.dom.compareNodeOrder = function(node1, node2) { + if(node1 == node2) { + return 0 + } + if(node1.compareDocumentPosition) { + return node1.compareDocumentPosition(node2) & 2 ? 1 : -1 + } + if("sourceIndex" in node1 || node1.parentNode && "sourceIndex" in node1.parentNode) { + var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT, isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT; + if(isElement1 && isElement2) { + return node1.sourceIndex - node2.sourceIndex + }else { + var parent1 = node1.parentNode, parent2 = node2.parentNode; + if(parent1 == parent2) { + return goog.dom.compareSiblingOrder_(node1, node2) + } + if(!isElement1 && goog.dom.contains(parent1, node2)) { + return-1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2) + } + if(!isElement2 && goog.dom.contains(parent2, node1)) { + return goog.dom.compareParentsDescendantNodeIe_(node2, node1) + } + return(isElement1 ? node1.sourceIndex : parent1.sourceIndex) - (isElement2 ? node2.sourceIndex : parent2.sourceIndex) + } + } + var doc = goog.dom.getOwnerDocument(node1), range1, range2; + range1 = doc.createRange(); + range1.selectNode(node1); + range1.collapse(true); + range2 = doc.createRange(); + range2.selectNode(node2); + range2.collapse(true); + return range1.compareBoundaryPoints(goog.global.Range.START_TO_END, range2) +}; +goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) { + var parent = textNode.parentNode; + if(parent == node) { + return-1 + } + for(var sibling = node;sibling.parentNode != parent;) { + sibling = sibling.parentNode + } + return goog.dom.compareSiblingOrder_(sibling, textNode) +}; +goog.dom.compareSiblingOrder_ = function(node1, node2) { + for(var s = node2;s = s.previousSibling;) { + if(s == node1) { + return-1 + } + } + return 1 +}; +goog.dom.findCommonAncestor = function() { + var i, count = arguments.length; + if(count) { + if(count == 1) { + return arguments[0] + } + }else { + return null + } + var paths = [], minLength = Infinity; + for(i = 0;i < count;i++) { + var ancestors = []; + for(var node = arguments[i];node;) { + ancestors.unshift(node); + node = node.parentNode + } + paths.push(ancestors); + minLength = Math.min(minLength, ancestors.length) + } + var output = null; + for(i = 0;i < minLength;i++) { + var first = paths[0][i]; + for(var j = 1;j < count;j++) { + if(first != paths[j][i]) { + return output + } + } + output = first + } + return output +}; +goog.dom.getOwnerDocument = function(node) { + return node.nodeType == goog.dom.NodeType.DOCUMENT ? node : node.ownerDocument || node.document +}; +goog.dom.getFrameContentDocument = function(frame) { + var doc; + return doc = goog.userAgent.WEBKIT ? frame.document || frame.contentWindow.document : frame.contentDocument || frame.contentWindow.document +}; +goog.dom.getFrameContentWindow = function(frame) { + return frame.contentWindow || goog.dom.getWindow_(goog.dom.getFrameContentDocument(frame)) +}; +goog.dom.setTextContent = function(element, text) { + if("textContent" in element) { + element.textContent = text + }else { + if(element.firstChild && element.firstChild.nodeType == goog.dom.NodeType.TEXT) { + for(;element.lastChild != element.firstChild;) { + element.removeChild(element.lastChild) + } + element.firstChild.data = text + }else { + goog.dom.removeChildren(element); + var doc = goog.dom.getOwnerDocument(element); + element.appendChild(doc.createTextNode(text)) + } + } +}; +goog.dom.getOuterHtml = function(element) { + if("outerHTML" in element) { + return element.outerHTML + }else { + var doc = goog.dom.getOwnerDocument(element), div = doc.createElement("div"); + div.appendChild(element.cloneNode(true)); + return div.innerHTML + } +}; +goog.dom.findNode = function(root, p) { + var rv = [], found = goog.dom.findNodes_(root, p, rv, true); + return found ? rv[0] : undefined +}; +goog.dom.findNodes = function(root, p) { + var rv = []; + goog.dom.findNodes_(root, p, rv, false); + return rv +}; +goog.dom.findNodes_ = function(root, p, rv, findOne) { + if(root != null) { + var i = 0; + for(var child;child = root.childNodes[i];i++) { + if(p(child)) { + rv.push(child); + if(findOne) { + return true + } + } + if(goog.dom.findNodes_(child, p, rv, findOne)) { + return true + } + } + } + return false +}; +goog.dom.TAGS_TO_IGNORE_ = {SCRIPT:1, STYLE:1, HEAD:1, IFRAME:1, OBJECT:1}; +goog.dom.PREDEFINED_TAG_VALUES_ = {IMG:" ", BR:"\n"}; +goog.dom.isFocusableTabIndex = function(element) { + var attrNode = element.getAttributeNode("tabindex"); + if(attrNode && attrNode.specified) { + var index = element.tabIndex; + return goog.isNumber(index) && index >= 0 + } + return false +}; +goog.dom.setFocusableTabIndex = function(element, enable) { + if(enable) { + element.tabIndex = 0 + }else { + element.removeAttribute("tabIndex") + } +}; +goog.dom.getTextContent = function(node) { + var textContent; + if(goog.userAgent.IE && "innerText" in node) { + textContent = goog.string.canonicalizeNewlines(node.innerText) + }else { + var buf = []; + goog.dom.getTextContent_(node, buf, true); + textContent = buf.join("") + } + textContent = textContent.replace(/\xAD/g, ""); + textContent = textContent.replace(/ +/g, " "); + if(textContent != " ") { + textContent = textContent.replace(/^\s*/, "") + } + return textContent +}; +goog.dom.getRawTextContent = function(node) { + var buf = []; + goog.dom.getTextContent_(node, buf, false); + return buf.join("") +}; +goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) { + if(!(node.nodeName in goog.dom.TAGS_TO_IGNORE_)) { + if(node.nodeType == goog.dom.NodeType.TEXT) { + normalizeWhitespace ? buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, "")) : buf.push(node.nodeValue) + }else { + if(node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]) + }else { + for(var child = node.firstChild;child;) { + goog.dom.getTextContent_(child, buf, normalizeWhitespace); + child = child.nextSibling + } + } + } + } +}; +goog.dom.getNodeTextLength = function(node) { + return goog.dom.getTextContent(node).length +}; +goog.dom.getNodeTextOffset = function(node, opt_offsetParent) { + var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body; + for(var buf = [];node && node != root;) { + for(var cur = node;cur = cur.previousSibling;) { + buf.unshift(goog.dom.getTextContent(cur)) + } + node = node.parentNode + } + return goog.string.trimLeft(buf.join("")).replace(/ +/g, " ").length +}; +goog.dom.getNodeAtOffset = function(parent, offset, opt_result) { + var stack = [parent], pos = 0; + for(var cur;stack.length > 0 && pos < offset;) { + cur = stack.pop(); + if(!(cur.nodeName in goog.dom.TAGS_TO_IGNORE_)) { + if(cur.nodeType == goog.dom.NodeType.TEXT) { + var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, "").replace(/ +/g, " "); + pos += text.length + }else { + if(cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) { + pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length + }else { + for(var i = cur.childNodes.length - 1;i >= 0;i--) { + stack.push(cur.childNodes[i]) + } + } + } + } + } + if(goog.isObject(opt_result)) { + opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0; + opt_result.node = cur + } + return cur +}; +goog.dom.isNodeList = function(val) { + if(val && typeof val.length == "number") { + if(goog.isObject(val)) { + return typeof val.item == "function" || typeof val.item == "string" + }else { + if(goog.isFunction(val)) { + return typeof val.item == "function" + } + } + } + return false +}; +goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class) { + var tagName = opt_tag ? opt_tag.toUpperCase() : null; + return goog.dom.getAncestor(element, function(node) { + return(!tagName || node.nodeName == tagName) && (!opt_class || goog.dom.classes.has(node, opt_class)) + }, true) +}; +goog.dom.getAncestor = function(element, matcher, opt_includeNode, opt_maxSearchSteps) { + if(!opt_includeNode) { + element = element.parentNode + } + var ignoreSearchSteps = opt_maxSearchSteps == null; + for(var steps = 0;element && (ignoreSearchSteps || steps <= opt_maxSearchSteps);) { + if(matcher(element)) { + return element + } + element = element.parentNode; + steps++ + } + return null +}; +goog.dom.DomHelper = function(opt_document) { + this.document_ = opt_document || goog.global.document || document +}; +a = goog.dom.DomHelper.prototype; +a.getDomHelper = goog.dom.getDomHelper; +a.getDocument = function() { + return this.document_ +}; +a.getElement = function(element) { + return goog.isString(element) ? this.document_.getElementById(element) : element +}; +a.$ = goog.dom.DomHelper.prototype.getElement; +a.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) { + return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag, opt_class, opt_el) +}; +a.$$ = goog.dom.DomHelper.prototype.getElementsByTagNameAndClass; +a.setProperties = goog.dom.setProperties; +a.getViewportSize = function(opt_window) { + return goog.dom.getViewportSize(opt_window || this.getWindow()) +}; +a.getDocumentHeight = function() { + return goog.dom.getDocumentHeight_(this.getWindow()) +}; +a.createDom = function() { + return goog.dom.createDom_(this.document_, arguments) +}; +a.$dom = goog.dom.DomHelper.prototype.createDom; +a.createElement = function(name) { + return this.document_.createElement(name) +}; +a.createTextNode = function(content) { + return this.document_.createTextNode(content) +}; +a.createTable = function(rows, columns, opt_fillWithNbsp) { + return goog.dom.createTable_(this.document_, rows, columns, !!opt_fillWithNbsp) +}; +a.htmlToDocumentFragment = function(htmlString) { + return goog.dom.htmlToDocumentFragment_(this.document_, htmlString) +}; +a.getCompatMode = function() { + return this.isCss1CompatMode() ? "CSS1Compat" : "BackCompat" +}; +a.isCss1CompatMode = function() { + return goog.dom.isCss1CompatMode_(this.document_) +}; +a.getWindow = function() { + return goog.dom.getWindow_(this.document_) +}; +a.getDocumentScrollElement = function() { + return goog.dom.getDocumentScrollElement_(this.document_) +}; +a.getDocumentScroll = function() { + return goog.dom.getDocumentScroll_(this.document_) +}; +a.appendChild = goog.dom.appendChild; +a.append = goog.dom.append; +a.removeChildren = goog.dom.removeChildren; +a.insertSiblingBefore = goog.dom.insertSiblingBefore; +a.insertSiblingAfter = goog.dom.insertSiblingAfter; +a.removeNode = goog.dom.removeNode; +a.replaceNode = goog.dom.replaceNode; +a.flattenElement = goog.dom.flattenElement; +a.getFirstElementChild = goog.dom.getFirstElementChild; +a.getLastElementChild = goog.dom.getLastElementChild; +a.getNextElementSibling = goog.dom.getNextElementSibling; +a.getPreviousElementSibling = goog.dom.getPreviousElementSibling; +a.getNextNode = goog.dom.getNextNode; +a.getPreviousNode = goog.dom.getPreviousNode; +a.isNodeLike = goog.dom.isNodeLike; +a.contains = goog.dom.contains; +a.getOwnerDocument = goog.dom.getOwnerDocument; +a.getFrameContentDocument = goog.dom.getFrameContentDocument; +a.getFrameContentWindow = goog.dom.getFrameContentWindow; +a.setTextContent = goog.dom.setTextContent; +a.findNode = goog.dom.findNode; +a.findNodes = goog.dom.findNodes; +a.getTextContent = goog.dom.getTextContent; +a.getNodeTextLength = goog.dom.getNodeTextLength; +a.getNodeTextOffset = goog.dom.getNodeTextOffset; +a.getAncestorByTagNameAndClass = goog.dom.getAncestorByTagNameAndClass; +a.getAncestor = goog.dom.getAncestor;goog.debug.errorHandlerWeakDep = {protectEntryPoint:function(fn) { + return fn +}};goog.iter = {}; +goog.iter.StopIteration = "StopIteration" in goog.global ? goog.global.StopIteration : Error("StopIteration"); +goog.iter.Iterator = function() { +}; +goog.iter.Iterator.prototype.next = function() { + throw goog.iter.StopIteration; +}; +goog.iter.Iterator.prototype.__iterator__ = function() { + return this +}; +goog.iter.toIterator = function(iterable) { + if(iterable instanceof goog.iter.Iterator) { + return iterable + } + if(typeof iterable.__iterator__ == "function") { + return iterable.__iterator__(false) + } + if(goog.isArrayLike(iterable)) { + var i = 0, newIter = new goog.iter.Iterator; + newIter.next = function() { + for(;;) { + if(i >= iterable.length) { + throw goog.iter.StopIteration; + } + if(i in iterable) { + return iterable[i++] + }else { + i++ + } + } + }; + return newIter + } + throw Error("Not implemented"); +}; +goog.iter.forEach = function(iterable, f, opt_obj) { + if(goog.isArrayLike(iterable)) { + try { + goog.array.forEach(iterable, f, opt_obj) + }catch(ex) { + if(ex !== goog.iter.StopIteration) { + throw ex; + } + } + }else { + iterable = goog.iter.toIterator(iterable); + try { + for(;;) { + f.call(opt_obj, iterable.next(), undefined, iterable) + } + }catch(ex$$0) { + if(ex$$0 !== goog.iter.StopIteration) { + throw ex$$0; + } + } + } +}; +goog.iter.filter = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + for(;;) { + var val = iterable.next(); + if(f.call(opt_obj, val, undefined, iterable)) { + return val + } + } + }; + return newIter +}; +goog.iter.range = function(startOrStop, opt_stop, opt_step) { + var start = 0, stop = startOrStop, step = opt_step || 1; + if(arguments.length > 1) { + start = startOrStop; + stop = opt_stop + } + if(step == 0) { + throw Error("Range step argument must not be zero"); + } + var newIter = new goog.iter.Iterator; + newIter.next = function() { + if(step > 0 && start >= stop || step < 0 && start <= stop) { + throw goog.iter.StopIteration; + } + var rv = start; + start += step; + return rv + }; + return newIter +}; +goog.iter.join = function(iterable, deliminator) { + return goog.iter.toArray(iterable).join(deliminator) +}; +goog.iter.map = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator; + newIter.next = function() { + for(;;) { + var val = iterable.next(); + return f.call(opt_obj, val, undefined, iterable) + } + }; + return newIter +}; +goog.iter.reduce = function(iterable, f, val$$0, opt_obj) { + var rval = val$$0; + goog.iter.forEach(iterable, function(val) { + rval = f.call(opt_obj, rval, val) + }); + return rval +}; +goog.iter.some = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + try { + for(;;) { + if(f.call(opt_obj, iterable.next(), undefined, iterable)) { + return true + } + } + }catch(ex) { + if(ex !== goog.iter.StopIteration) { + throw ex; + } + } + return false +}; +goog.iter.every = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + try { + for(;;) { + if(!f.call(opt_obj, iterable.next(), undefined, iterable)) { + return false + } + } + }catch(ex) { + if(ex !== goog.iter.StopIteration) { + throw ex; + } + } + return true +}; +goog.iter.chain = function() { + var args = arguments, length = args.length, i = 0, newIter = new goog.iter.Iterator; + newIter.next = function() { + try { + if(i >= length) { + throw goog.iter.StopIteration; + } + var current = goog.iter.toIterator(args[i]); + return current.next() + }catch(ex) { + if(ex !== goog.iter.StopIteration || i >= length) { + throw ex; + }else { + i++; + return this.next() + } + } + }; + return newIter +}; +goog.iter.dropWhile = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator, dropping = true; + newIter.next = function() { + for(;;) { + var val = iterable.next(); + if(!(dropping && f.call(opt_obj, val, undefined, iterable))) { + dropping = false; + return val + } + } + }; + return newIter +}; +goog.iter.takeWhile = function(iterable, f, opt_obj) { + iterable = goog.iter.toIterator(iterable); + var newIter = new goog.iter.Iterator, taking = true; + newIter.next = function() { + for(;;) { + if(taking) { + var val = iterable.next(); + if(f.call(opt_obj, val, undefined, iterable)) { + return val + }else { + taking = false + } + }else { + throw goog.iter.StopIteration; + } + } + }; + return newIter +}; +goog.iter.toArray = function(iterable) { + if(goog.isArrayLike(iterable)) { + return goog.array.toArray(iterable) + } + iterable = goog.iter.toIterator(iterable); + var array = []; + goog.iter.forEach(iterable, function(val) { + array.push(val) + }); + return array +}; +goog.iter.equals = function(iterable1, iterable2) { + iterable1 = goog.iter.toIterator(iterable1); + iterable2 = goog.iter.toIterator(iterable2); + var b1, b2; + try { + for(;;) { + b1 = b2 = false; + var val1 = iterable1.next(); + b1 = true; + var val2 = iterable2.next(); + b2 = true; + if(val1 != val2) { + return false + } + } + }catch(ex) { + if(ex !== goog.iter.StopIteration) { + throw ex; + }else { + if(b1 && !b2) { + return false + } + if(!b2) { + try { + iterable2.next(); + return false + }catch(ex1) { + if(ex1 !== goog.iter.StopIteration) { + throw ex1; + } + return true + } + } + } + } + return false +}; +goog.iter.nextOrValue = function(iterable, defaultValue) { + try { + return goog.iter.toIterator(iterable).next() + }catch(e) { + if(e != goog.iter.StopIteration) { + throw e; + } + return defaultValue + } +};goog.structs = {}; +goog.structs.getCount = function(col) { + if(typeof col.getCount == "function") { + return col.getCount() + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return col.length + } + return goog.object.getCount(col) +}; +goog.structs.getValues = function(col) { + if(typeof col.getValues == "function") { + return col.getValues() + } + if(goog.isString(col)) { + return col.split("") + } + if(goog.isArrayLike(col)) { + var rv = [], l = col.length; + for(var i = 0;i < l;i++) { + rv.push(col[i]) + } + return rv + } + return goog.object.getValues(col) +}; +goog.structs.getKeys = function(col) { + if(typeof col.getKeys == "function") { + return col.getKeys() + } + if(typeof col.getValues != "function") { + if(goog.isArrayLike(col) || goog.isString(col)) { + var rv = [], l = col.length; + for(var i = 0;i < l;i++) { + rv.push(i) + } + return rv + } + return goog.object.getKeys(col) + } +}; +goog.structs.contains = function(col, val) { + if(typeof col.contains == "function") { + return col.contains(val) + } + if(typeof col.containsValue == "function") { + return col.containsValue(val) + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.contains(col, val) + } + return goog.object.containsValue(col, val) +}; +goog.structs.isEmpty = function(col) { + if(typeof col.isEmpty == "function") { + return col.isEmpty() + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.isEmpty(col) + } + return goog.object.isEmpty(col) +}; +goog.structs.clear = function(col) { + if(typeof col.clear == "function") { + col.clear() + }else { + goog.isArrayLike(col) ? goog.array.clear(col) : goog.object.clear(col) + } +}; +goog.structs.forEach = function(col, f, opt_obj) { + if(typeof col.forEach == "function") { + col.forEach(f, opt_obj) + }else { + if(goog.isArrayLike(col) || goog.isString(col)) { + goog.array.forEach(col, f, opt_obj) + }else { + var keys = goog.structs.getKeys(col), values = goog.structs.getValues(col), l = values.length; + for(var i = 0;i < l;i++) { + f.call(opt_obj, values[i], keys && keys[i], col) + } + } + } +}; +goog.structs.filter = function(col, f, opt_obj) { + if(typeof col.filter == "function") { + return col.filter(f, opt_obj) + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.filter(col, f, opt_obj) + } + var rv, keys = goog.structs.getKeys(col), values = goog.structs.getValues(col), l = values.length; + if(keys) { + rv = {}; + for(var i = 0;i < l;i++) { + if(f.call(opt_obj, values[i], keys[i], col)) { + rv[keys[i]] = values[i] + } + } + }else { + rv = []; + for(i = 0;i < l;i++) { + f.call(opt_obj, values[i], undefined, col) && rv.push(values[i]) + } + } + return rv +}; +goog.structs.map = function(col, f, opt_obj) { + if(typeof col.map == "function") { + return col.map(f, opt_obj) + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.map(col, f, opt_obj) + } + var rv, keys = goog.structs.getKeys(col), values = goog.structs.getValues(col), l = values.length; + if(keys) { + rv = {}; + for(var i = 0;i < l;i++) { + rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col) + } + }else { + rv = []; + for(i = 0;i < l;i++) { + rv[i] = f.call(opt_obj, values[i], undefined, col) + } + } + return rv +}; +goog.structs.some = function(col, f, opt_obj) { + if(typeof col.some == "function") { + return col.some(f, opt_obj) + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.some(col, f, opt_obj) + } + var keys = goog.structs.getKeys(col), values = goog.structs.getValues(col), l = values.length; + for(var i = 0;i < l;i++) { + if(f.call(opt_obj, values[i], keys && keys[i], col)) { + return true + } + } + return false +}; +goog.structs.every = function(col, f, opt_obj) { + if(typeof col.every == "function") { + return col.every(f, opt_obj) + } + if(goog.isArrayLike(col) || goog.isString(col)) { + return goog.array.every(col, f, opt_obj) + } + var keys = goog.structs.getKeys(col), values = goog.structs.getValues(col), l = values.length; + for(var i = 0;i < l;i++) { + if(!f.call(opt_obj, values[i], keys && keys[i], col)) { + return false + } + } + return true +};goog.structs.Map = function(opt_map) { + this.map_ = {}; + this.keys_ = []; + var argLength = arguments.length; + if(argLength > 1) { + if(argLength % 2) { + throw Error("Uneven number of arguments"); + } + for(var i = 0;i < argLength;i += 2) { + this.set(arguments[i], arguments[i + 1]) + } + }else { + opt_map && this.addAll(opt_map) + } +}; +a = goog.structs.Map.prototype; +a.count_ = 0; +a.version_ = 0; +a.getCount = function() { + return this.count_ +}; +a.getValues = function() { + this.cleanupKeysArray_(); + var rv = []; + for(var i = 0;i < this.keys_.length;i++) { + var key = this.keys_[i]; + rv.push(this.map_[key]) + } + return rv +}; +a.getKeys = function() { + this.cleanupKeysArray_(); + return this.keys_.concat() +}; +a.containsKey = function(key) { + return goog.structs.Map.hasKey_(this.map_, key) +}; +a.containsValue = function(val) { + for(var i = 0;i < this.keys_.length;i++) { + var key = this.keys_[i]; + if(goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) { + return true + } + } + return false +}; +a.equals = function(otherMap, opt_equalityFn) { + if(this === otherMap) { + return true + } + if(this.count_ != otherMap.getCount()) { + return false + } + var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals; + this.cleanupKeysArray_(); + var key; + for(var i = 0;key = this.keys_[i];i++) { + if(!equalityFn(this.get(key), otherMap.get(key))) { + return false + } + } + return true +}; +goog.structs.Map.defaultEquals = function(a, b) { + return a === b +}; +a = goog.structs.Map.prototype; +a.isEmpty = function() { + return this.count_ == 0 +}; +a.clear = function() { + this.map_ = {}; + this.version_ = this.count_ = this.keys_.length = 0 +}; +a.remove = function(key) { + if(goog.structs.Map.hasKey_(this.map_, key)) { + delete this.map_[key]; + this.count_--; + this.version_++; + this.keys_.length > 2 * this.count_ && this.cleanupKeysArray_(); + return true + } + return false +}; +a.cleanupKeysArray_ = function() { + if(this.count_ != this.keys_.length) { + var srcIndex = 0; + for(var destIndex = 0;srcIndex < this.keys_.length;) { + var key = this.keys_[srcIndex]; + if(goog.structs.Map.hasKey_(this.map_, key)) { + this.keys_[destIndex++] = key + } + srcIndex++ + } + this.keys_.length = destIndex + } + if(this.count_ != this.keys_.length) { + var seen = {}; + srcIndex = 0; + for(destIndex = 0;srcIndex < this.keys_.length;) { + key = this.keys_[srcIndex]; + if(!goog.structs.Map.hasKey_(seen, key)) { + this.keys_[destIndex++] = key; + seen[key] = 1 + } + srcIndex++ + } + this.keys_.length = destIndex + } +}; +a.get = function(key, opt_val) { + if(goog.structs.Map.hasKey_(this.map_, key)) { + return this.map_[key] + } + return opt_val +}; +a.set = function(key, value) { + if(!goog.structs.Map.hasKey_(this.map_, key)) { + this.count_++; + this.keys_.push(key); + this.version_++ + } + this.map_[key] = value +}; +a.addAll = function(map) { + var keys, values; + if(map instanceof goog.structs.Map) { + keys = map.getKeys(); + values = map.getValues() + }else { + keys = goog.object.getKeys(map); + values = goog.object.getValues(map) + } + for(var i = 0;i < keys.length;i++) { + this.set(keys[i], values[i]) + } +}; +a.clone = function() { + return new goog.structs.Map(this) +}; +a.transpose = function() { + var transposed = new goog.structs.Map; + for(var i = 0;i < this.keys_.length;i++) { + var key = this.keys_[i], value = this.map_[key]; + transposed.set(value, key) + } + return transposed +}; +a.__iterator__ = function(opt_keys) { + this.cleanupKeysArray_(); + var i = 0, keys = this.keys_, map = this.map_, version = this.version_, selfObj = this, newIter = new goog.iter.Iterator; + newIter.next = function() { + for(;;) { + if(version != selfObj.version_) { + throw Error("The map has changed since the iterator was created"); + } + if(i >= keys.length) { + throw goog.iter.StopIteration; + } + var key = keys[i++]; + return opt_keys ? key : map[key] + } + }; + return newIter +}; +goog.structs.Map.hasKey_ = function(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key) +};goog.structs.Set = function(opt_values) { + this.map_ = new goog.structs.Map; + opt_values && this.addAll(opt_values) +}; +goog.structs.Set.getKey_ = function(val) { + var type = typeof val; + return type == "object" && val || type == "function" ? "o" + goog.getUid(val) : type.substr(0, 1) + val +}; +a = goog.structs.Set.prototype; +a.getCount = function() { + return this.map_.getCount() +}; +a.add = function(element) { + this.map_.set(goog.structs.Set.getKey_(element), element) +}; +a.addAll = function(col) { + var values = goog.structs.getValues(col), l = values.length; + for(var i = 0;i < l;i++) { + this.add(values[i]) + } +}; +a.removeAll = function(col) { + var values = goog.structs.getValues(col), l = values.length; + for(var i = 0;i < l;i++) { + this.remove(values[i]) + } +}; +a.remove = function(element) { + return this.map_.remove(goog.structs.Set.getKey_(element)) +}; +a.clear = function() { + this.map_.clear() +}; +a.isEmpty = function() { + return this.map_.isEmpty() +}; +a.contains = function(element) { + return this.map_.containsKey(goog.structs.Set.getKey_(element)) +}; +a.getValues = function() { + return this.map_.getValues() +}; +a.clone = function() { + return new goog.structs.Set(this) +}; +a.equals = function(col) { + return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col) +}; +a.isSubsetOf = function(col) { + var colCount = goog.structs.getCount(col); + if(this.getCount() > colCount) { + return false + } + if(!(col instanceof goog.structs.Set) && colCount > 5) { + col = new goog.structs.Set(col) + } + return goog.structs.every(this, function(value) { + return goog.structs.contains(col, value) + }) +}; +a.__iterator__ = function() { + return this.map_.__iterator__(false) +};goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) { + var target = opt_target || goog.global, oldErrorHandler = target.onerror; + target.onerror = function(message, url, line) { + oldErrorHandler && oldErrorHandler(message, url, line); + var file = String(url).split(/[\/\\]/).pop(); + logFunc({message:message, fileName:file, line:line}); + return Boolean(opt_cancel) + } +}; +goog.debug.expose = function(obj, opt_showFn) { + if(typeof obj == "undefined") { + return"undefined" + } + if(obj == null) { + return"NULL" + } + var str = []; + for(var x in obj) { + if(!(!opt_showFn && goog.isFunction(obj[x]))) { + var s = x + " = "; + try { + s += obj[x] + }catch(e) { + s += "*** " + e + " ***" + } + str.push(s) + } + } + return str.join("\n") +}; +goog.debug.deepExpose = function(obj$$0, opt_showFn) { + var previous = new goog.structs.Set, str = [], helper = function(obj, space) { + var nestspace = space + " "; + try { + if(goog.isDef(obj)) { + if(goog.isNull(obj)) { + str.push("NULL") + }else { + if(goog.isString(obj)) { + str.push('"' + obj.replace(/\n/g, "\n" + space) + '"') + }else { + if(goog.isFunction(obj)) { + str.push(String(obj).replace(/\n/g, "\n" + space)) + }else { + if(goog.isObject(obj)) { + if(previous.contains(obj)) { + str.push("*** reference loop detected ***") + }else { + previous.add(obj); + str.push("{"); + for(var x in obj) { + if(!(!opt_showFn && goog.isFunction(obj[x]))) { + str.push("\n"); + str.push(nestspace); + str.push(x + " = "); + helper(obj[x], nestspace) + } + } + str.push("\n" + space + "}") + } + }else { + str.push(obj) + } + } + } + } + }else { + str.push("undefined") + } + }catch(e) { + str.push("*** " + e + " ***") + } + }; + helper(obj$$0, ""); + return str.join("") +}; +goog.debug.exposeArray = function(arr) { + var str = []; + for(var i = 0;i < arr.length;i++) { + goog.isArray(arr[i]) ? str.push(goog.debug.exposeArray(arr[i])) : str.push(arr[i]) + } + return"[ " + str.join(", ") + " ]" +}; +goog.debug.exposeException = function(err, opt_fn) { + try { + var e = goog.debug.normalizeErrorObject(err), error = "Message: " + goog.string.htmlEscape(e.message) + '\nUrl:
' + e.fileName + "\nLine: " + e.lineNumber + "\n\nBrowser stack:\n" + goog.string.htmlEscape(e.stack + "-> ") + "[end]\n\nJS stack traversal:\n" + goog.string.htmlEscape(goog.debug.getStacktrace(opt_fn) + "-> "); + return error + }catch(e2) { + return"Exception trying to expose exception! You win, we lose. " + e2 + } +}; +goog.debug.normalizeErrorObject = function(err) { + var href = goog.getObjectByName("window.location.href"); + return typeof err == "string" ? {message:err, name:"Unknown error", lineNumber:"Not available", fileName:href, stack:"Not available"} : !err.lineNumber || !err.fileName || !err.stack ? {message:err.message, name:err.name, lineNumber:err.lineNumber || err.line || "Not available", fileName:err.fileName || err.filename || err.sourceURL || href, stack:err.stack || "Not available"} : err +}; +goog.debug.enhanceError = function(err, opt_message) { + var error = typeof err == "string" ? Error(err) : err; + if(!error.stack) { + error.stack = goog.debug.getStacktrace(arguments.callee.caller) + } + if(opt_message) { + for(var x = 0;error["message" + x];) { + ++x + } + error["message" + x] = String(opt_message) + } + return error +}; +goog.debug.getStacktraceSimple = function(opt_depth) { + var sb = [], fn = arguments.callee.caller; + for(var depth = 0;fn && (!opt_depth || depth < opt_depth);) { + sb.push(goog.debug.getFunctionName(fn)); + sb.push("()\n"); + try { + fn = fn.caller + }catch(e) { + sb.push("[exception trying to get caller]\n"); + break + } + depth++; + if(depth >= goog.debug.MAX_STACK_DEPTH) { + sb.push("[...long stack...]"); + break + } + } + opt_depth && depth >= opt_depth ? sb.push("[...reached max depth limit...]") : sb.push("[end]"); + return sb.join("") +}; +goog.debug.MAX_STACK_DEPTH = 50; +goog.debug.getStacktrace = function(opt_fn) { + return goog.debug.getStacktraceHelper_(opt_fn || arguments.callee.caller, []) +}; +goog.debug.getStacktraceHelper_ = function(fn, visited) { + var sb = []; + if(goog.array.contains(visited, fn)) { + sb.push("[...circular reference...]") + }else { + if(fn && visited.length < goog.debug.MAX_STACK_DEPTH) { + sb.push(goog.debug.getFunctionName(fn) + "("); + var args = fn.arguments; + for(var i = 0;i < args.length;i++) { + i > 0 && sb.push(", "); + var argDesc, arg = args[i]; + switch(typeof arg) { + case "object": + argDesc = arg ? "object" : "null"; + break; + case "string": + argDesc = arg; + break; + case "number": + argDesc = String(arg); + break; + case "boolean": + argDesc = arg ? "true" : "false"; + break; + case "function": + argDesc = (argDesc = goog.debug.getFunctionName(arg)) ? argDesc : "[fn]"; + break; + case "undefined": + ; + default: + argDesc = typeof arg; + break + } + if(argDesc.length > 40) { + argDesc = argDesc.substr(0, 40) + "..." + } + sb.push(argDesc) + } + visited.push(fn); + sb.push(")\n"); + try { + sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited)) + }catch(e) { + sb.push("[exception trying to get caller]\n") + } + }else { + fn ? sb.push("[...long stack...]") : sb.push("[end]") + } + } + return sb.join("") +}; +goog.debug.getFunctionName = function(fn) { + var functionSource = String(fn); + if(!goog.debug.fnNameCache_[functionSource]) { + var matches = /function ([^\(]+)/.exec(functionSource); + if(matches) { + var method = matches[1]; + goog.debug.fnNameCache_[functionSource] = method + }else { + goog.debug.fnNameCache_[functionSource] = "[Anonymous]" + } + } + return goog.debug.fnNameCache_[functionSource] +}; +goog.debug.makeWhitespaceVisible = function(string) { + return string.replace(/ /g, "[_]").replace(/\f/g, "[f]").replace(/\n/g, "[n]\n").replace(/\r/g, "[r]").replace(/\t/g, "[t]") +}; +goog.debug.fnNameCache_ = {};goog.debug.LogRecord = function(level, msg, loggerName, opt_time, opt_sequenceNumber) { + this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber) +}; +goog.debug.LogRecord.prototype.sequenceNumber_ = 0; +goog.debug.LogRecord.prototype.exception_ = null; +goog.debug.LogRecord.prototype.exceptionText_ = null; +goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS = true; +goog.debug.LogRecord.nextSequenceNumber_ = 0; +goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName, opt_time, opt_sequenceNumber) { + if(goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) { + this.sequenceNumber_ = typeof opt_sequenceNumber == "number" ? opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++ + } + this.time_ = opt_time || goog.now(); + this.level_ = level; + this.msg_ = msg; + this.loggerName_ = loggerName; + delete this.exception_; + delete this.exceptionText_ +}; +goog.debug.LogRecord.prototype.setException = function(exception) { + this.exception_ = exception +}; +goog.debug.LogRecord.prototype.setExceptionText = function(text) { + this.exceptionText_ = text +}; +goog.debug.LogRecord.prototype.setLevel = function(level) { + this.level_ = level +};goog.debug.LogBuffer = function() { + goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(), "Cannot use goog.debug.LogBuffer without defining goog.debug.LogBuffer.CAPACITY."); + this.clear() +}; +goog.debug.LogBuffer.getInstance = function() { + if(!goog.debug.LogBuffer.instance_) { + goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer + } + return goog.debug.LogBuffer.instance_ +}; +goog.debug.LogBuffer.CAPACITY = 0; +goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) { + var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY; + this.curIndex_ = curIndex; + if(this.isFull_) { + var ret = this.buffer_[curIndex]; + ret.reset(level, msg, loggerName); + return ret + } + this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1; + return this.buffer_[curIndex] = new goog.debug.LogRecord(level, msg, loggerName) +}; +goog.debug.LogBuffer.isBufferingEnabled = function() { + return goog.debug.LogBuffer.CAPACITY > 0 +}; +goog.debug.LogBuffer.prototype.clear = function() { + this.buffer_ = Array(goog.debug.LogBuffer.CAPACITY); + this.curIndex_ = -1; + this.isFull_ = false +};goog.debug.Logger = function(name) { + this.name_ = name +}; +goog.debug.Logger.prototype.parent_ = null; +goog.debug.Logger.prototype.level_ = null; +goog.debug.Logger.prototype.children_ = null; +goog.debug.Logger.prototype.handlers_ = null; +goog.debug.Logger.ENABLE_HIERARCHY = true; +if(!goog.debug.Logger.ENABLE_HIERARCHY) { + goog.debug.Logger.rootHandlers_ = [] +} +goog.debug.Logger.Level = function(name, value) { + this.name = name; + this.value = value +}; +goog.debug.Logger.Level.prototype.toString = function() { + return this.name +}; +goog.debug.Logger.Level.OFF = new goog.debug.Logger.Level("OFF", Infinity); +goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level("SHOUT", 1200); +goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level("SEVERE", 1E3); +goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level("WARNING", 900); +goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level("INFO", 800); +goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level("CONFIG", 700); +goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level("FINE", 500); +goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level("FINER", 400); +goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level("FINEST", 300); +goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level("ALL", 0); +goog.debug.Logger.Level.PREDEFINED_LEVELS = [goog.debug.Logger.Level.OFF, goog.debug.Logger.Level.SHOUT, goog.debug.Logger.Level.SEVERE, goog.debug.Logger.Level.WARNING, goog.debug.Logger.Level.INFO, goog.debug.Logger.Level.CONFIG, goog.debug.Logger.Level.FINE, goog.debug.Logger.Level.FINER, goog.debug.Logger.Level.FINEST, goog.debug.Logger.Level.ALL]; +goog.debug.Logger.Level.predefinedLevelsCache_ = null; +goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() { + goog.debug.Logger.Level.predefinedLevelsCache_ = {}; + var i = 0; + for(var level;level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];i++) { + goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level; + goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level + } +}; +goog.debug.Logger.Level.getPredefinedLevel = function(name) { + goog.debug.Logger.Level.predefinedLevelsCache_ || goog.debug.Logger.Level.createPredefinedLevelsCache_(); + return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null +}; +goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) { + goog.debug.Logger.Level.predefinedLevelsCache_ || goog.debug.Logger.Level.createPredefinedLevelsCache_(); + if(value in goog.debug.Logger.Level.predefinedLevelsCache_) { + return goog.debug.Logger.Level.predefinedLevelsCache_[value] + } + for(var i = 0;i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length;++i) { + var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i]; + if(level.value <= value) { + return level + } + } + return null +}; +goog.debug.Logger.getLogger = function(name) { + return goog.debug.LogManager.getLogger(name) +}; +a = goog.debug.Logger.prototype; +a.getParent = function() { + return this.parent_ +}; +a.getChildren = function() { + if(!this.children_) { + this.children_ = {} + } + return this.children_ +}; +a.setLevel = function(level) { + if(goog.debug.Logger.ENABLE_HIERARCHY) { + this.level_ = level + }else { + goog.asserts.assert(!this.name_, "Cannot call setLevel() on a non-root logger when goog.debug.Logger.ENABLE_HIERARCHY is false."); + goog.debug.Logger.rootLevel_ = level + } +}; +a.getEffectiveLevel = function() { + if(!goog.debug.Logger.ENABLE_HIERARCHY) { + return goog.debug.Logger.rootLevel_ + } + if(this.level_) { + return this.level_ + } + if(this.parent_) { + return this.parent_.getEffectiveLevel() + } + goog.asserts.fail("Root logger has no level set."); + return null +}; +a.isLoggable = function(level) { + return level.value >= this.getEffectiveLevel().value +}; +a.log = function(level, msg, opt_exception) { + this.isLoggable(level) && this.doLogRecord_(this.getLogRecord(level, msg, opt_exception)) +}; +a.getLogRecord = function(level, msg, opt_exception) { + var logRecord = goog.debug.LogBuffer.isBufferingEnabled() ? goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_) : new goog.debug.LogRecord(level, String(msg), this.name_); + if(opt_exception) { + logRecord.setException(opt_exception); + logRecord.setExceptionText(goog.debug.exposeException(opt_exception, arguments.callee.caller)) + } + return logRecord +}; +a.severe = function(msg, opt_exception) { + this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception) +}; +a.warning = function(msg, opt_exception) { + this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception) +}; +a.fine = function(msg, opt_exception) { + this.log(goog.debug.Logger.Level.FINE, msg, opt_exception) +}; +a.finest = function(msg, opt_exception) { + this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception) +}; +a.doLogRecord_ = function(logRecord) { + if(goog.debug.Logger.ENABLE_HIERARCHY) { + for(var target = this;target;) { + target.callPublish_(logRecord); + target = target.getParent() + } + }else { + var i = 0; + for(var handler;handler = goog.debug.Logger.rootHandlers_[i++];) { + handler(logRecord) + } + } +}; +a.callPublish_ = function(logRecord) { + if(this.handlers_) { + var i = 0; + for(var handler;handler = this.handlers_[i];i++) { + handler(logRecord) + } + } +}; +a.setParent_ = function(parent) { + this.parent_ = parent +}; +a.addChild_ = function(name, logger) { + this.getChildren()[name] = logger +}; +goog.debug.LogManager = {}; +goog.debug.LogManager.loggers_ = {}; +goog.debug.LogManager.rootLogger_ = null; +goog.debug.LogManager.initialize = function() { + if(!goog.debug.LogManager.rootLogger_) { + goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(""); + goog.debug.LogManager.loggers_[""] = goog.debug.LogManager.rootLogger_; + goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG) + } +}; +goog.debug.LogManager.getLoggers = function() { + return goog.debug.LogManager.loggers_ +}; +goog.debug.LogManager.getRoot = function() { + goog.debug.LogManager.initialize(); + return goog.debug.LogManager.rootLogger_ +}; +goog.debug.LogManager.getLogger = function(name) { + goog.debug.LogManager.initialize(); + var ret = goog.debug.LogManager.loggers_[name]; + return ret || goog.debug.LogManager.createLogger_(name) +}; +goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) { + return function(info) { + var logger = opt_logger || goog.debug.LogManager.getRoot(); + logger.severe("Error: " + info.message + " (" + info.fileName + " @ Line: " + info.line + ")") + } +}; +goog.debug.LogManager.createLogger_ = function(name) { + var logger = new goog.debug.Logger(name); + if(goog.debug.Logger.ENABLE_HIERARCHY) { + var lastDotIndex = name.lastIndexOf("."), parentName = name.substr(0, lastDotIndex), leafName = name.substr(lastDotIndex + 1), parentLogger = goog.debug.LogManager.getLogger(parentName); + parentLogger.addChild_(leafName, logger); + logger.setParent_(parentLogger) + } + return goog.debug.LogManager.loggers_[name] = logger +};goog.Disposable = function() { +}; +goog.Disposable.prototype.disposed_ = false; +goog.Disposable.prototype.dispose = function() { + if(!this.disposed_) { + this.disposed_ = true; + this.disposeInternal() + } +}; +goog.Disposable.prototype.disposeInternal = function() { +}; +goog.dispose = function(obj) { + obj && typeof obj.dispose == "function" && obj.dispose() +};goog.events = {}; +goog.events.Event = function(type, opt_target) { + this.type = type; + this.currentTarget = this.target = opt_target +}; +goog.inherits(goog.events.Event, goog.Disposable); +a = goog.events.Event.prototype; +a.disposeInternal = function() { + delete this.type; + delete this.target; + delete this.currentTarget +}; +a.propagationStopped_ = false; +a.returnValue_ = true; +a.stopPropagation = function() { + this.propagationStopped_ = true +}; +a.preventDefault = function() { + this.returnValue_ = false +}; +goog.events.Event.stopPropagation = function(e) { + e.stopPropagation() +}; +goog.events.Event.preventDefault = function(e) { + e.preventDefault() +};goog.events.BrowserEvent = function(opt_e, opt_currentTarget) { + opt_e && this.init(opt_e, opt_currentTarget) +}; +goog.inherits(goog.events.BrowserEvent, goog.events.Event); +goog.events.BrowserEvent.MouseButton = {LEFT:0, MIDDLE:1, RIGHT:2}; +goog.events.BrowserEvent.IEButtonMap_ = [1, 4, 2]; +a = goog.events.BrowserEvent.prototype; +a.target = null; +a.relatedTarget = null; +a.offsetX = 0; +a.offsetY = 0; +a.clientX = 0; +a.clientY = 0; +a.screenX = 0; +a.screenY = 0; +a.button = 0; +a.keyCode = 0; +a.charCode = 0; +a.ctrlKey = false; +a.altKey = false; +a.shiftKey = false; +a.metaKey = false; +a.platformModifierKey = false; +a.event_ = null; +a.init = function(e, opt_currentTarget) { + var type = this.type = e.type; + this.target = e.target || e.srcElement; + this.currentTarget = opt_currentTarget; + var relatedTarget = e.relatedTarget; + if(relatedTarget) { + if(goog.userAgent.GECKO) { + try { + relatedTarget = relatedTarget.nodeName && relatedTarget + }catch(err) { + relatedTarget = null + } + } + }else { + if(type == "mouseover") { + relatedTarget = e.fromElement + }else { + if(type == "mouseout") { + relatedTarget = e.toElement + } + } + } + this.relatedTarget = relatedTarget; + this.offsetX = e.offsetX !== undefined ? e.offsetX : e.layerX; + this.offsetY = e.offsetY !== undefined ? e.offsetY : e.layerY; + this.clientX = e.clientX !== undefined ? e.clientX : e.pageX; + this.clientY = e.clientY !== undefined ? e.clientY : e.pageY; + this.screenX = e.screenX || 0; + this.screenY = e.screenY || 0; + this.button = e.button; + this.keyCode = e.keyCode || 0; + this.charCode = e.charCode || (type == "keypress" ? e.keyCode : 0); + this.ctrlKey = e.ctrlKey; + this.altKey = e.altKey; + this.shiftKey = e.shiftKey; + this.metaKey = e.metaKey; + this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey; + this.event_ = e; + delete this.returnValue_; + delete this.propagationStopped_ +}; +a.stopPropagation = function() { + this.propagationStopped_ = true; + if(this.event_.stopPropagation) { + this.event_.stopPropagation() + }else { + this.event_.cancelBubble = true + } +}; +goog.events.BrowserEvent.IE7_SET_KEY_CODE_TO_PREVENT_DEFAULT_ = goog.userAgent.IE && !goog.userAgent.isVersion("8"); +goog.events.BrowserEvent.prototype.preventDefault = function() { + this.returnValue_ = false; + var be = this.event_; + if(be.preventDefault) { + be.preventDefault() + }else { + be.returnValue = false; + if(goog.events.BrowserEvent.IE7_SET_KEY_CODE_TO_PREVENT_DEFAULT_) { + try { + if(be.ctrlKey || be.keyCode >= 112 && be.keyCode <= 123) { + be.keyCode = -1 + } + }catch(ex) { + } + } + } +}; +goog.events.BrowserEvent.prototype.disposeInternal = function() { + goog.events.BrowserEvent.superClass_.disposeInternal.call(this); + this.relatedTarget = this.currentTarget = this.target = this.event_ = null +};goog.events.EventWrapper = function() { +}; +goog.events.EventWrapper.prototype.listen = function() { +}; +goog.events.EventWrapper.prototype.unlisten = function() { +};goog.structs.SimplePool = function(initialCount, maxCount) { + this.maxCount_ = maxCount; + this.freeQueue_ = []; + this.createInitial_(initialCount) +}; +goog.inherits(goog.structs.SimplePool, goog.Disposable); +a = goog.structs.SimplePool.prototype; +a.createObjectFn_ = null; +a.disposeObjectFn_ = null; +a.setCreateObjectFn = function(createObjectFn) { + this.createObjectFn_ = createObjectFn +}; +a.getObject = function() { + if(this.freeQueue_.length) { + return this.freeQueue_.pop() + } + return this.createObject() +}; +a.releaseObject = function(obj) { + this.freeQueue_.length < this.maxCount_ ? this.freeQueue_.push(obj) : this.disposeObject(obj) +}; +a.createInitial_ = function(initialCount) { + if(initialCount > this.maxCount_) { + throw Error("[goog.structs.SimplePool] Initial cannot be greater than max"); + } + for(var i = 0;i < initialCount;i++) { + this.freeQueue_.push(this.createObject()) + } +}; +a.createObject = function() { + return this.createObjectFn_ ? this.createObjectFn_() : {} +}; +a.disposeObject = function(obj) { + if(this.disposeObjectFn_) { + this.disposeObjectFn_(obj) + }else { + if(goog.isFunction(obj.dispose)) { + obj.dispose() + }else { + for(var i in obj) { + delete obj[i] + } + } + } +}; +a.disposeInternal = function() { + goog.structs.SimplePool.superClass_.disposeInternal.call(this); + for(var freeQueue = this.freeQueue_;freeQueue.length;) { + this.disposeObject(freeQueue.pop()) + } + delete this.freeQueue_ +};goog.userAgent.jscript = {}; +goog.userAgent.jscript.ASSUME_NO_JSCRIPT = false; +goog.userAgent.jscript.init_ = function() { + var hasScriptEngine = "ScriptEngine" in goog.global; + goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ = hasScriptEngine && goog.global.ScriptEngine() == "JScript"; + goog.userAgent.jscript.DETECTED_VERSION_ = goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_ ? goog.global.ScriptEngineMajorVersion() + "." + goog.global.ScriptEngineMinorVersion() + "." + goog.global.ScriptEngineBuildVersion() : "0" +}; +goog.userAgent.jscript.ASSUME_NO_JSCRIPT || goog.userAgent.jscript.init_(); +goog.userAgent.jscript.HAS_JSCRIPT = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ? false : goog.userAgent.jscript.DETECTED_HAS_JSCRIPT_; +goog.userAgent.jscript.VERSION = goog.userAgent.jscript.ASSUME_NO_JSCRIPT ? "0" : goog.userAgent.jscript.DETECTED_VERSION_; +goog.userAgent.jscript.isVersion = function(version) { + return goog.string.compareVersions(goog.userAgent.jscript.VERSION, version) >= 0 +};goog.events.Listener = function() { +}; +goog.events.Listener.counter_ = 0; +a = goog.events.Listener.prototype; +a.key = 0; +a.removed = false; +a.callOnce = false; +a.init = function(listener, proxy, src, type, capture, opt_handler) { + if(goog.isFunction(listener)) { + this.isFunctionListener_ = true + }else { + if(listener && listener.handleEvent && goog.isFunction(listener.handleEvent)) { + this.isFunctionListener_ = false + }else { + throw Error("Invalid listener argument"); + } + } + this.listener = listener; + this.proxy = proxy; + this.src = src; + this.type = type; + this.capture = !!capture; + this.handler = opt_handler; + this.callOnce = false; + this.key = ++goog.events.Listener.counter_; + this.removed = false +}; +a.handleEvent = function(eventObject) { + if(this.isFunctionListener_) { + return this.listener.call(this.handler || this.src, eventObject) + } + return this.listener.handleEvent.call(this.listener, eventObject) +};goog.events.pools = {}; +(function() { + function getObject() { + return{count_:0, remaining_:0} + } + function getArray() { + return[] + } + function getProxy() { + var f = function(eventObject) { + return proxyCallbackFunction.call(f.src, f.key, eventObject) + }; + return f + } + function getListener() { + return new goog.events.Listener + } + function getEvent() { + return new goog.events.BrowserEvent + } + var BAD_GC = goog.userAgent.jscript.HAS_JSCRIPT && !goog.userAgent.jscript.isVersion("5.7"), proxyCallbackFunction; + goog.events.pools.setProxyCallbackFunction = function(cb) { + proxyCallbackFunction = cb + }; + if(BAD_GC) { + goog.events.pools.getObject = function() { + return objectPool.getObject() + }; + goog.events.pools.releaseObject = function(obj) { + objectPool.releaseObject(obj) + }; + goog.events.pools.getArray = function() { + return arrayPool.getObject() + }; + goog.events.pools.releaseArray = function(obj) { + arrayPool.releaseObject(obj) + }; + goog.events.pools.getProxy = function() { + return proxyPool.getObject() + }; + goog.events.pools.releaseProxy = function() { + proxyPool.releaseObject(getProxy()) + }; + goog.events.pools.getListener = function() { + return listenerPool.getObject() + }; + goog.events.pools.releaseListener = function(obj) { + listenerPool.releaseObject(obj) + }; + goog.events.pools.getEvent = function() { + return eventPool.getObject() + }; + goog.events.pools.releaseEvent = function(obj) { + eventPool.releaseObject(obj) + }; + var objectPool = new goog.structs.SimplePool(0, 600); + objectPool.setCreateObjectFn(getObject); + var arrayPool = new goog.structs.SimplePool(0, 600); + arrayPool.setCreateObjectFn(getArray); + var proxyPool = new goog.structs.SimplePool(0, 600); + proxyPool.setCreateObjectFn(getProxy); + var listenerPool = new goog.structs.SimplePool(0, 600); + listenerPool.setCreateObjectFn(getListener); + var eventPool = new goog.structs.SimplePool(0, 600); + eventPool.setCreateObjectFn(getEvent) + }else { + goog.events.pools.getObject = getObject; + goog.events.pools.releaseObject = goog.nullFunction; + goog.events.pools.getArray = getArray; + goog.events.pools.releaseArray = goog.nullFunction; + goog.events.pools.getProxy = getProxy; + goog.events.pools.releaseProxy = goog.nullFunction; + goog.events.pools.getListener = getListener; + goog.events.pools.releaseListener = goog.nullFunction; + goog.events.pools.getEvent = getEvent; + goog.events.pools.releaseEvent = goog.nullFunction + } +})();goog.events.listeners_ = {}; +goog.events.listenerTree_ = {}; +goog.events.sources_ = {}; +goog.events.onString_ = "on"; +goog.events.onStringMap_ = {}; +goog.events.keySeparator_ = "_"; +goog.events.listen = function(src, type, listener, opt_capt, opt_handler) { + if(type) { + if(goog.isArray(type)) { + for(var i = 0;i < type.length;i++) { + goog.events.listen(src, type[i], listener, opt_capt, opt_handler) + } + return null + }else { + var capture = !!opt_capt, map = goog.events.listenerTree_; + type in map || (map[type] = goog.events.pools.getObject()); + map = map[type]; + if(!(capture in map)) { + map[capture] = goog.events.pools.getObject(); + map.count_++ + } + map = map[capture]; + var srcUid = goog.getUid(src), listenerArray, listenerObj; + map.remaining_++; + if(map[srcUid]) { + listenerArray = map[srcUid]; + for(i = 0;i < listenerArray.length;i++) { + listenerObj = listenerArray[i]; + if(listenerObj.listener == listener && listenerObj.handler == opt_handler) { + if(listenerObj.removed) { + break + } + return listenerArray[i].key + } + } + }else { + listenerArray = map[srcUid] = goog.events.pools.getArray(); + map.count_++ + } + var proxy = goog.events.pools.getProxy(); + proxy.src = src; + listenerObj = goog.events.pools.getListener(); + listenerObj.init(listener, proxy, src, type, capture, opt_handler); + var key = listenerObj.key; + proxy.key = key; + listenerArray.push(listenerObj); + goog.events.listeners_[key] = listenerObj; + goog.events.sources_[srcUid] || (goog.events.sources_[srcUid] = goog.events.pools.getArray()); + goog.events.sources_[srcUid].push(listenerObj); + if(src.addEventListener) { + if(src == goog.global || !src.customEvent_) { + src.addEventListener(type, proxy, capture) + } + }else { + src.attachEvent(goog.events.getOnString_(type), proxy) + } + return key + } + }else { + throw Error("Invalid event type"); + } +}; +goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) { + if(goog.isArray(type)) { + for(var i = 0;i < type.length;i++) { + goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler) + } + return null + } + var key = goog.events.listen(src, type, listener, opt_capt, opt_handler), listenerObj = goog.events.listeners_[key]; + listenerObj.callOnce = true; + return key +}; +goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) { + wrapper.listen(src, listener, opt_capt, opt_handler) +}; +goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) { + if(goog.isArray(type)) { + for(var i = 0;i < type.length;i++) { + goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler) + } + return null + } + var capture = !!opt_capt, listenerArray = goog.events.getListeners_(src, type, capture); + if(!listenerArray) { + return false + } + for(i = 0;i < listenerArray.length;i++) { + if(listenerArray[i].listener == listener && listenerArray[i].capture == capture && listenerArray[i].handler == opt_handler) { + return goog.events.unlistenByKey(listenerArray[i].key) + } + } + return false +}; +goog.events.unlistenByKey = function(key) { + if(!goog.events.listeners_[key]) { + return false + } + var listener = goog.events.listeners_[key]; + if(listener.removed) { + return false + } + var src = listener.src, type = listener.type, proxy = listener.proxy, capture = listener.capture; + if(src.removeEventListener) { + if(src == goog.global || !src.customEvent_) { + src.removeEventListener(type, proxy, capture) + } + }else { + src.detachEvent && src.detachEvent(goog.events.getOnString_(type), proxy) + } + var srcUid = goog.getUid(src), listenerArray = goog.events.listenerTree_[type][capture][srcUid]; + if(goog.events.sources_[srcUid]) { + var sourcesArray = goog.events.sources_[srcUid]; + goog.array.remove(sourcesArray, listener); + sourcesArray.length == 0 && delete goog.events.sources_[srcUid] + } + listener.removed = true; + listenerArray.needsCleanup_ = true; + goog.events.cleanUp_(type, capture, srcUid, listenerArray); + delete goog.events.listeners_[key]; + return true +}; +goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) { + wrapper.unlisten(src, listener, opt_capt, opt_handler) +}; +goog.events.cleanUp_ = function(type, capture, srcUid, listenerArray) { + if(!listenerArray.locked_) { + if(listenerArray.needsCleanup_) { + var oldIndex = 0; + for(var newIndex = 0;oldIndex < listenerArray.length;oldIndex++) { + if(listenerArray[oldIndex].removed) { + var proxy = listenerArray[oldIndex].proxy; + proxy.src = null; + goog.events.pools.releaseProxy(proxy); + goog.events.pools.releaseListener(listenerArray[oldIndex]) + }else { + if(oldIndex != newIndex) { + listenerArray[newIndex] = listenerArray[oldIndex] + } + newIndex++ + } + } + listenerArray.length = newIndex; + listenerArray.needsCleanup_ = false; + if(newIndex == 0) { + goog.events.pools.releaseArray(listenerArray); + delete goog.events.listenerTree_[type][capture][srcUid]; + goog.events.listenerTree_[type][capture].count_--; + if(goog.events.listenerTree_[type][capture].count_ == 0) { + goog.events.pools.releaseObject(goog.events.listenerTree_[type][capture]); + delete goog.events.listenerTree_[type][capture]; + goog.events.listenerTree_[type].count_-- + } + if(goog.events.listenerTree_[type].count_ == 0) { + goog.events.pools.releaseObject(goog.events.listenerTree_[type]); + delete goog.events.listenerTree_[type] + } + } + } + } +}; +goog.events.removeAll = function(opt_obj, opt_type, opt_capt) { + var count = 0, noObj = opt_obj == null, noType = opt_type == null, noCapt = opt_capt == null; + opt_capt = !!opt_capt; + if(noObj) { + goog.object.forEach(goog.events.sources_, function(listeners) { + for(var i = listeners.length - 1;i >= 0;i--) { + var listener = listeners[i]; + if((noType || opt_type == listener.type) && (noCapt || opt_capt == listener.capture)) { + goog.events.unlistenByKey(listener.key); + count++ + } + } + }) + }else { + var srcUid = goog.getUid(opt_obj); + if(goog.events.sources_[srcUid]) { + var sourcesArray = goog.events.sources_[srcUid]; + for(var i$$0 = sourcesArray.length - 1;i$$0 >= 0;i$$0--) { + var listener$$0 = sourcesArray[i$$0]; + if((noType || opt_type == listener$$0.type) && (noCapt || opt_capt == listener$$0.capture)) { + goog.events.unlistenByKey(listener$$0.key); + count++ + } + } + } + } + return count +}; +goog.events.getListeners = function(obj, type, capture) { + return goog.events.getListeners_(obj, type, capture) || [] +}; +goog.events.getListeners_ = function(obj, type, capture) { + var map = goog.events.listenerTree_; + if(type in map) { + map = map[type]; + if(capture in map) { + map = map[capture]; + var objUid = goog.getUid(obj); + if(map[objUid]) { + return map[objUid] + } + } + } + return null +}; +goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) { + var capture = !!opt_capt, listenerArray = goog.events.getListeners_(src, type, capture); + if(listenerArray) { + for(var i = 0;i < listenerArray.length;i++) { + if(listenerArray[i].listener == listener && listenerArray[i].capture == capture && listenerArray[i].handler == opt_handler) { + return listenerArray[i] + } + } + } + return null +}; +goog.events.hasListener = function(obj, opt_type, opt_capture) { + var objUid = goog.getUid(obj), listeners = goog.events.sources_[objUid]; + if(listeners) { + var hasType = goog.isDef(opt_type), hasCapture = goog.isDef(opt_capture); + if(hasType && hasCapture) { + var map = goog.events.listenerTree_[opt_type]; + return!!map && !!map[opt_capture] && objUid in map[opt_capture] + }else { + return hasType || hasCapture ? goog.array.some(listeners, function(listener) { + return hasType && listener.type == opt_type || hasCapture && listener.capture == opt_capture + }) : true + } + } + return false +}; +goog.events.expose = function(e) { + var str = []; + for(var key in e) { + e[key] && e[key].id ? str.push(key + " = " + e[key] + " (" + e[key].id + ")") : str.push(key + " = " + e[key]) + } + return str.join("\n") +}; +goog.events.EventType = {CLICK:"click", DBLCLICK:"dblclick", MOUSEDOWN:"mousedown", MOUSEUP:"mouseup", MOUSEOVER:"mouseover", MOUSEOUT:"mouseout", MOUSEMOVE:"mousemove", SELECTSTART:"selectstart", KEYPRESS:"keypress", KEYDOWN:"keydown", KEYUP:"keyup", BLUR:"blur", FOCUS:"focus", DEACTIVATE:"deactivate", FOCUSIN:goog.userAgent.IE ? "focusin" : "DOMFocusIn", FOCUSOUT:goog.userAgent.IE ? "focusout" : "DOMFocusOut", CHANGE:"change", SELECT:"select", SUBMIT:"submit", DRAGSTART:"dragstart", DRAGENTER:"dragenter", +DRAGOVER:"dragover", DRAGLEAVE:"dragleave", DROP:"drop", CONTEXTMENU:"contextmenu", ERROR:"error", HELP:"help", LOAD:"load", LOSECAPTURE:"losecapture", READYSTATECHANGE:"readystatechange", RESIZE:"resize", SCROLL:"scroll", UNLOAD:"unload", HASHCHANGE:"hashchange", POPSTATE:"popstate"}; +goog.events.getOnString_ = function(type) { + if(type in goog.events.onStringMap_) { + return goog.events.onStringMap_[type] + } + return goog.events.onStringMap_[type] = goog.events.onString_ + type +}; +goog.events.fireListeners = function(obj, type, capture, eventObject) { + var map = goog.events.listenerTree_; + if(type in map) { + map = map[type]; + if(capture in map) { + return goog.events.fireListeners_(map[capture], obj, type, capture, eventObject) + } + } + return true +}; +goog.events.fireListeners_ = function(map, obj, type, capture, eventObject) { + var retval = 1, objUid = goog.getUid(obj); + if(map[objUid]) { + map.remaining_--; + var listenerArray = map[objUid]; + if(listenerArray.locked_) { + listenerArray.locked_++ + }else { + listenerArray.locked_ = 1 + } + try { + var length = listenerArray.length; + for(var i = 0;i < length;i++) { + var listener = listenerArray[i]; + if(listener && !listener.removed) { + retval &= goog.events.fireListener(listener, eventObject) !== false + } + } + }finally { + listenerArray.locked_--; + goog.events.cleanUp_(type, capture, objUid, listenerArray) + } + } + return Boolean(retval) +}; +goog.events.fireListener = function(listener, eventObject) { + var rv = listener.handleEvent(eventObject); + listener.callOnce && goog.events.unlistenByKey(listener.key); + return rv +}; +goog.events.getTotalListenerCount = function() { + return goog.object.getCount(goog.events.listeners_) +}; +goog.events.dispatchEvent = function(src, e) { + if(goog.isString(e)) { + e = new goog.events.Event(e, src) + }else { + if(e instanceof goog.events.Event) { + e.target = e.target || src + }else { + var oldEvent = e; + e = new goog.events.Event(e.type, src); + goog.object.extend(e, oldEvent) + } + } + var rv = 1, ancestors, type = e.type, map = goog.events.listenerTree_; + if(!(type in map)) { + return true + } + map = map[type]; + var hasCapture = true in map, targetsMap; + if(hasCapture) { + ancestors = []; + for(var parent = src;parent;parent = parent.getParentEventTarget()) { + ancestors.push(parent) + } + targetsMap = map[true]; + targetsMap.remaining_ = targetsMap.count_; + for(var i = ancestors.length - 1;!e.propagationStopped_ && i >= 0 && targetsMap.remaining_;i--) { + e.currentTarget = ancestors[i]; + rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, true, e) && e.returnValue_ != false + } + } + var hasBubble = false in map; + if(hasBubble) { + targetsMap = map[false]; + targetsMap.remaining_ = targetsMap.count_; + if(hasCapture) { + for(i = 0;!e.propagationStopped_ && i < ancestors.length && targetsMap.remaining_;i++) { + e.currentTarget = ancestors[i]; + rv &= goog.events.fireListeners_(targetsMap, ancestors[i], e.type, false, e) && e.returnValue_ != false + } + }else { + for(var current = src;!e.propagationStopped_ && current && targetsMap.remaining_;current = current.getParentEventTarget()) { + e.currentTarget = current; + rv &= goog.events.fireListeners_(targetsMap, current, e.type, false, e) && e.returnValue_ != false + } + } + } + return Boolean(rv) +}; +goog.events.protectBrowserEventEntryPoint = function(errorHandler, opt_tracers) { + goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(goog.events.handleBrowserEvent_, opt_tracers); + goog.events.pools.setProxyCallbackFunction(goog.events.handleBrowserEvent_) +}; +goog.events.handleBrowserEvent_ = function(key, opt_evt) { + if(!goog.events.listeners_[key]) { + return true + } + var listener = goog.events.listeners_[key], type = listener.type, map = goog.events.listenerTree_; + if(!(type in map)) { + return true + } + map = map[type]; + var retval, targetsMap; + if(goog.events.synthesizeEventPropagation_()) { + var ieEvent = opt_evt || goog.getObjectByName("window.event"), hasCapture = true in map, hasBubble = false in map; + if(hasCapture) { + if(goog.events.isMarkedIeEvent_(ieEvent)) { + return true + } + goog.events.markIeEvent_(ieEvent) + } + var evt = goog.events.pools.getEvent(); + evt.init(ieEvent, this); + retval = true; + try { + if(hasCapture) { + var ancestors = goog.events.pools.getArray(); + for(var parent = evt.currentTarget;parent;parent = parent.parentNode) { + ancestors.push(parent) + } + targetsMap = map[true]; + targetsMap.remaining_ = targetsMap.count_; + for(var i = ancestors.length - 1;!evt.propagationStopped_ && i >= 0 && targetsMap.remaining_;i--) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, true, evt) + } + if(hasBubble) { + targetsMap = map[false]; + targetsMap.remaining_ = targetsMap.count_; + for(i = 0;!evt.propagationStopped_ && i < ancestors.length && targetsMap.remaining_;i++) { + evt.currentTarget = ancestors[i]; + retval &= goog.events.fireListeners_(targetsMap, ancestors[i], type, false, evt) + } + } + }else { + retval = goog.events.fireListener(listener, evt) + } + }finally { + if(ancestors) { + ancestors.length = 0; + goog.events.pools.releaseArray(ancestors) + } + evt.dispose(); + goog.events.pools.releaseEvent(evt) + } + return retval + } + var be = new goog.events.BrowserEvent(opt_evt, this); + try { + retval = goog.events.fireListener(listener, be) + }finally { + be.dispose() + } + return retval +}; +goog.events.pools.setProxyCallbackFunction(goog.events.handleBrowserEvent_); +goog.events.markIeEvent_ = function(e) { + var useReturnValue = false; + if(e.keyCode == 0) { + try { + e.keyCode = -1; + return + }catch(ex) { + useReturnValue = true + } + } + if(useReturnValue || e.returnValue == undefined) { + e.returnValue = true + } +}; +goog.events.isMarkedIeEvent_ = function(e) { + return e.keyCode < 0 || e.returnValue != undefined +}; +goog.events.uniqueIdCounter_ = 0; +goog.events.getUniqueId = function(identifier) { + return identifier + "_" + goog.events.uniqueIdCounter_++ +}; +goog.events.synthesizeEventPropagation_ = function() { + if(goog.events.requiresSyntheticEventPropagation_ === undefined) { + goog.events.requiresSyntheticEventPropagation_ = goog.userAgent.IE && !goog.global.addEventListener + } + return goog.events.requiresSyntheticEventPropagation_ +};goog.events.EventTarget = function() { +}; +goog.inherits(goog.events.EventTarget, goog.Disposable); +a = goog.events.EventTarget.prototype; +a.customEvent_ = true; +a.parentEventTarget_ = null; +a.getParentEventTarget = function() { + return this.parentEventTarget_ +}; +a.addEventListener = function(type, handler, opt_capture, opt_handlerScope) { + goog.events.listen(this, type, handler, opt_capture, opt_handlerScope) +}; +a.removeEventListener = function(type, handler, opt_capture, opt_handlerScope) { + goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope) +}; +a.dispatchEvent = function(e) { + return goog.events.dispatchEvent(this, e) +}; +a.disposeInternal = function() { + goog.events.EventTarget.superClass_.disposeInternal.call(this); + goog.events.removeAll(this); + this.parentEventTarget_ = null +};goog.json = {}; +goog.json.isValid_ = function(s) { + if(/^\s*$/.test(s)) { + return false + } + var backslashesRe = /\\["\\\/bfnrtu]/g, simpleValuesRe = /"[^"\\\n\r\u2028\u2029\x00-\x08\x10-\x1f\x80-\x9f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g, remainderRe = /^[\],:{}\s\u2028\u2029]*$/; + return remainderRe.test(s.replace(backslashesRe, "@").replace(simpleValuesRe, "]").replace(openBracketsRe, "")) +}; +goog.json.parse = function(s) { + var o = String(s); + if(goog.json.isValid_(o)) { + try { + return eval("(" + o + ")") + }catch(ex) { + } + } + throw Error("Invalid JSON string: " + o); +}; +goog.json.unsafeParse = function(s) { + return eval("(" + s + ")") +}; +goog.json.serialize = function(object) { + return(new goog.json.Serializer).serialize(object) +}; +goog.json.Serializer = function() { +}; +goog.json.Serializer.prototype.serialize = function(object) { + var sb = []; + this.serialize_(object, sb); + return sb.join("") +}; +goog.json.Serializer.prototype.serialize_ = function(object, sb) { + switch(typeof object) { + case "string": + this.serializeString_(object, sb); + break; + case "number": + this.serializeNumber_(object, sb); + break; + case "boolean": + sb.push(object); + break; + case "undefined": + sb.push("null"); + break; + case "object": + if(object == null) { + sb.push("null"); + break + } + if(goog.isArray(object)) { + this.serializeArray_(object, sb); + break + } + this.serializeObject_(object, sb); + break; + case "function": + break; + default: + throw Error("Unknown type: " + typeof object); + } +}; +goog.json.Serializer.charToJsonCharCache_ = {'"':'\\"', "\\":"\\\\", "/":"\\/", "\u0008":"\\b", "\u000c":"\\f", "\n":"\\n", "\r":"\\r", "\t":"\\t", "\u000b":"\\u000b"}; +goog.json.Serializer.charsToReplace_ = /\uffff/.test("\uffff") ? /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g; +goog.json.Serializer.prototype.serializeString_ = function(s, sb) { + sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) { + if(c in goog.json.Serializer.charToJsonCharCache_) { + return goog.json.Serializer.charToJsonCharCache_[c] + } + var cc = c.charCodeAt(0), rv = "\\u"; + if(cc < 16) { + rv += "000" + }else { + if(cc < 256) { + rv += "00" + }else { + if(cc < 4096) { + rv += "0" + } + } + } + return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16) + }), '"') +}; +goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) { + sb.push(isFinite(n) && !isNaN(n) ? n : "null") +}; +goog.json.Serializer.prototype.serializeArray_ = function(arr, sb) { + var l = arr.length; + sb.push("["); + var sep = ""; + for(var i = 0;i < l;i++) { + sb.push(sep); + this.serialize_(arr[i], sb); + sep = "," + } + sb.push("]") +}; +goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) { + sb.push("{"); + var sep = ""; + for(var key in obj) { + if(obj.hasOwnProperty(key)) { + var value = obj[key]; + if(typeof value != "function") { + sb.push(sep); + this.serializeString_(key, sb); + sb.push(":"); + this.serialize_(value, sb); + sep = "," + } + } + } + sb.push("}") +};goog.Timer = function(opt_interval, opt_timerObject) { + this.interval_ = opt_interval || 1; + this.timerObject_ = opt_timerObject || goog.Timer.defaultTimerObject; + this.boundTick_ = goog.bind(this.tick_, this); + this.last_ = goog.now() +}; +goog.inherits(goog.Timer, goog.events.EventTarget); +goog.Timer.MAX_TIMEOUT_ = 2147483647; +goog.Timer.prototype.enabled = false; +goog.Timer.defaultTimerObject = goog.global.window; +goog.Timer.intervalScale = 0.8; +a = goog.Timer.prototype; +a.timer_ = null; +a.setInterval = function(interval) { + this.interval_ = interval; + if(this.timer_ && this.enabled) { + this.stop(); + this.start() + }else { + this.timer_ && this.stop() + } +}; +a.tick_ = function() { + if(this.enabled) { + var elapsed = goog.now() - this.last_; + if(elapsed > 0 && elapsed < this.interval_ * goog.Timer.intervalScale) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_ - elapsed) + }else { + this.dispatchTick(); + if(this.enabled) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_); + this.last_ = goog.now() + } + } + } +}; +a.dispatchTick = function() { + this.dispatchEvent(goog.Timer.TICK) +}; +a.start = function() { + this.enabled = true; + if(!this.timer_) { + this.timer_ = this.timerObject_.setTimeout(this.boundTick_, this.interval_); + this.last_ = goog.now() + } +}; +a.stop = function() { + this.enabled = false; + if(this.timer_) { + this.timerObject_.clearTimeout(this.timer_); + this.timer_ = null + } +}; +a.disposeInternal = function() { + goog.Timer.superClass_.disposeInternal.call(this); + this.stop(); + delete this.timerObject_ +}; +goog.Timer.TICK = "tick"; +goog.Timer.callOnce = function(listener, opt_delay, opt_handler) { + if(goog.isFunction(listener)) { + if(opt_handler) { + listener = goog.bind(listener, opt_handler) + } + }else { + if(listener && typeof listener.handleEvent == "function") { + listener = goog.bind(listener.handleEvent, listener) + }else { + throw Error("Invalid listener argument"); + } + } + return opt_delay > goog.Timer.MAX_TIMEOUT_ ? -1 : goog.Timer.defaultTimerObject.setTimeout(listener, opt_delay || 0) +}; +goog.Timer.clear = function(timerId) { + goog.Timer.defaultTimerObject.clearTimeout(timerId) +};goog.net = {}; +goog.net.ErrorCode = {NO_ERROR:0, ACCESS_DENIED:1, FILE_NOT_FOUND:2, FF_SILENT_ERROR:3, CUSTOM_ERROR:4, EXCEPTION:5, HTTP_ERROR:6, ABORT:7, TIMEOUT:8, OFFLINE:9}; +goog.net.ErrorCode.getDebugMessage = function(errorCode) { + switch(errorCode) { + case goog.net.ErrorCode.NO_ERROR: + return"No Error"; + case goog.net.ErrorCode.ACCESS_DENIED: + return"Access denied to content document"; + case goog.net.ErrorCode.FILE_NOT_FOUND: + return"File not found"; + case goog.net.ErrorCode.FF_SILENT_ERROR: + return"Firefox silently errored"; + case goog.net.ErrorCode.CUSTOM_ERROR: + return"Application custom error"; + case goog.net.ErrorCode.EXCEPTION: + return"An exception occurred"; + case goog.net.ErrorCode.HTTP_ERROR: + return"Http response at 400 or 500 level"; + case goog.net.ErrorCode.ABORT: + return"Request was aborted"; + case goog.net.ErrorCode.TIMEOUT: + return"Request timed out"; + case goog.net.ErrorCode.OFFLINE: + return"The resource is not available offline"; + default: + return"Unrecognized error code" + } +};goog.net.EventType = {COMPLETE:"complete", SUCCESS:"success", ERROR:"error", ABORT:"abort", READY:"ready", READY_STATE_CHANGE:"readystatechange", TIMEOUT:"timeout", INCREMENTAL_DATA:"incrementaldata", PROGRESS:"progress"};goog.net.XhrMonitor_ = function() { + if(goog.userAgent.GECKO) { + this.contextsToXhr_ = {}; + this.xhrToContexts_ = {}; + this.stack_ = [] + } +}; +goog.net.XhrMonitor_.getKey = function(obj) { + return goog.isString(obj) ? obj : goog.isObject(obj) ? goog.getUid(obj) : "" +}; +a = goog.net.XhrMonitor_.prototype; +a.logger_ = goog.debug.Logger.getLogger("goog.net.xhrMonitor"); +a.enabled_ = goog.userAgent.GECKO; +a.pushContext = function(context) { + if(this.enabled_) { + var key = goog.net.XhrMonitor_.getKey(context); + this.logger_.finest("Pushing context: " + context + " (" + key + ")"); + this.stack_.push(key) + } +}; +a.popContext = function() { + if(this.enabled_) { + var context = this.stack_.pop(); + this.logger_.finest("Popping context: " + context); + this.updateDependentContexts_(context) + } +}; +a.markXhrOpen = function(xhr) { + if(this.enabled_) { + var uid = goog.getUid(xhr); + this.logger_.fine("Opening XHR : " + uid); + for(var i = 0;i < this.stack_.length;i++) { + var context = this.stack_[i]; + this.addToMap_(this.contextsToXhr_, context, uid); + this.addToMap_(this.xhrToContexts_, uid, context) + } + } +}; +a.markXhrClosed = function(xhr) { + if(this.enabled_) { + var uid = goog.getUid(xhr); + this.logger_.fine("Closing XHR : " + uid); + delete this.xhrToContexts_[uid]; + for(var context in this.contextsToXhr_) { + goog.array.remove(this.contextsToXhr_[context], uid); + this.contextsToXhr_[context].length == 0 && delete this.contextsToXhr_[context] + } + } +}; +a.updateDependentContexts_ = function(xhrUid) { + var contexts = this.xhrToContexts_[xhrUid], xhrs = this.contextsToXhr_[xhrUid]; + if(contexts && xhrs) { + this.logger_.finest("Updating dependent contexts"); + goog.array.forEach(contexts, function(context) { + goog.array.forEach(xhrs, function(xhr) { + this.addToMap_(this.contextsToXhr_, context, xhr); + this.addToMap_(this.xhrToContexts_, xhr, context) + }, this) + }, this) + } +}; +a.addToMap_ = function(map, key, value) { + map[key] || (map[key] = []); + goog.array.contains(map[key], value) || map[key].push(value) +}; +goog.net.xhrMonitor = new goog.net.XhrMonitor_;goog.net.XmlHttpFactory = function() { +}; +goog.net.XmlHttpFactory.prototype.cachedOptions_ = null; +goog.net.XmlHttpFactory.prototype.getOptions = function() { + return this.cachedOptions_ || (this.cachedOptions_ = this.internalGetOptions()) +};goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) { + this.xhrFactory_ = xhrFactory; + this.optionsFactory_ = optionsFactory +}; +goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory); +goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() { + return this.xhrFactory_() +}; +goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() { + return this.optionsFactory_() +};goog.net.XmlHttp = function() { + return goog.net.XmlHttp.create() +}; +goog.net.XmlHttp.create = function() { + return goog.net.XmlHttp.factory_.createInstance() +}; +goog.net.XmlHttp.getOptions = function() { + return goog.net.XmlHttp.factory_.getOptions() +}; +goog.net.XmlHttp.OptionType = {USE_NULL_FUNCTION:0, LOCAL_REQUEST_ERROR:1}; +goog.net.XmlHttp.ReadyState = {UNINITIALIZED:0, LOADING:1, LOADED:2, INTERACTIVE:3, COMPLETE:4}; +goog.net.XmlHttp.setFactory = function(factory, optionsFactory) { + goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(factory, optionsFactory)) +}; +goog.net.XmlHttp.setGlobalFactory = function(factory) { + goog.net.XmlHttp.factory_ = factory +}; +goog.net.DefaultXmlHttpFactory = function() { +}; +goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory); +goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() { + var progId = this.getProgId_(); + return progId ? new ActiveXObject(progId) : new XMLHttpRequest +}; +goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() { + var progId = this.getProgId_(), options = {}; + if(progId) { + options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true; + options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true + } + return options +}; +goog.net.DefaultXmlHttpFactory.prototype.ieProgId_ = null; +goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() { + if(!this.ieProgId_ && typeof XMLHttpRequest == "undefined" && typeof ActiveXObject != "undefined") { + var ACTIVE_X_IDENTS = ["MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"]; + for(var i = 0;i < ACTIVE_X_IDENTS.length;i++) { + var candidate = ACTIVE_X_IDENTS[i]; + try { + new ActiveXObject(candidate); + return this.ieProgId_ = candidate + }catch(e) { + } + } + throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed"); + } + return this.ieProgId_ +}; +goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory);goog.net.XhrIo = function(opt_xmlHttpFactory) { + this.headers = new goog.structs.Map; + this.xmlHttpFactory_ = opt_xmlHttpFactory || null +}; +goog.inherits(goog.net.XhrIo, goog.events.EventTarget); +goog.net.XhrIo.prototype.logger_ = goog.debug.Logger.getLogger("goog.net.XhrIo"); +goog.net.XhrIo.CONTENT_TYPE_HEADER = "Content-Type"; +goog.net.XhrIo.FORM_CONTENT_TYPE = "application/x-www-form-urlencoded;charset=utf-8"; +goog.net.XhrIo.sendInstances_ = []; +goog.net.XhrIo.send = function(url, opt_callback, opt_method, opt_content, opt_headers, opt_timeoutInterval) { + var x = new goog.net.XhrIo; + goog.net.XhrIo.sendInstances_.push(x); + opt_callback && goog.events.listen(x, goog.net.EventType.COMPLETE, opt_callback); + goog.events.listen(x, goog.net.EventType.READY, goog.partial(goog.net.XhrIo.cleanupSend_, x)); + opt_timeoutInterval && x.setTimeoutInterval(opt_timeoutInterval); + x.send(url, opt_method, opt_content, opt_headers) +}; +goog.net.XhrIo.cleanup = function() { + for(var instances = goog.net.XhrIo.sendInstances_;instances.length;) { + instances.pop().dispose() + } +}; +goog.net.XhrIo.protectEntryPoints = function(errorHandler, opt_tracers) { + goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_ = errorHandler.protectEntryPoint(goog.net.XhrIo.prototype.onReadyStateChangeEntryPoint_, opt_tracers) +}; +goog.net.XhrIo.cleanupSend_ = function(XhrIo) { + XhrIo.dispose(); + goog.array.remove(goog.net.XhrIo.sendInstances_, XhrIo) +}; +a = goog.net.XhrIo.prototype; +a.active_ = false; +a.xhr_ = null; +a.xhrOptions_ = null; +a.lastUri_ = ""; +a.lastMethod_ = ""; +a.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; +a.lastError_ = ""; +a.errorDispatched_ = false; +a.inSend_ = false; +a.inOpen_ = false; +a.inAbort_ = false; +a.timeoutInterval_ = 0; +a.timeoutId_ = null; +a.setTimeoutInterval = function(ms) { + this.timeoutInterval_ = Math.max(0, ms) +}; +a.send = function(url, opt_method, opt_content, opt_headers) { + if(this.active_) { + throw Error("[goog.net.XhrIo] Object is active with another request"); + } + var method = opt_method || "GET"; + this.lastUri_ = url; + this.lastError_ = ""; + this.lastErrorCode_ = goog.net.ErrorCode.NO_ERROR; + this.lastMethod_ = method; + this.errorDispatched_ = false; + this.active_ = true; + this.xhr_ = this.createXhr(); + this.xhrOptions_ = this.xmlHttpFactory_ ? this.xmlHttpFactory_.getOptions() : goog.net.XmlHttp.getOptions(); + goog.net.xhrMonitor.markXhrOpen(this.xhr_); + this.xhr_.onreadystatechange = goog.bind(this.onReadyStateChange_, this); + try { + this.logger_.fine(this.formatMsg_("Opening Xhr")); + this.inOpen_ = true; + this.xhr_.open(method, url, true); + this.inOpen_ = false + }catch(err) { + this.logger_.fine(this.formatMsg_("Error opening Xhr: " + err.message)); + this.error_(goog.net.ErrorCode.EXCEPTION, err); + return + } + var content = opt_content || "", headers = this.headers.clone(); + opt_headers && goog.structs.forEach(opt_headers, function(value, key) { + headers.set(key, value) + }); + method == "POST" && !headers.containsKey(goog.net.XhrIo.CONTENT_TYPE_HEADER) && headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER, goog.net.XhrIo.FORM_CONTENT_TYPE); + goog.structs.forEach(headers, function(value, key) { + this.xhr_.setRequestHeader(key, value) + }, this); + try { + if(this.timeoutId_) { + goog.Timer.defaultTimerObject.clearTimeout(this.timeoutId_); + this.timeoutId_ = null + } + if(this.timeoutInterval_ > 0) { + this.logger_.fine(this.formatMsg_("Will abort after " + this.timeoutInterval_ + "ms if incomplete")); + this.timeoutId_ = goog.Timer.defaultTimerObject.setTimeout(goog.bind(this.timeout_, this), this.timeoutInterval_) + } + this.logger_.fine(this.formatMsg_("Sending request")); + this.inSend_ = true; + this.xhr_.send(content); + this.inSend_ = false + }catch(err$$0) { + this.logger_.fine(this.formatMsg_("Send error: " + err$$0.message)); + this.error_(goog.net.ErrorCode.EXCEPTION, err$$0) + } +}; +a.createXhr = function() { + return this.xmlHttpFactory_ ? this.xmlHttpFactory_.createInstance() : goog.net.XmlHttp.create() +}; +a.dispatchEvent = function(e) { + if(this.xhr_) { + goog.net.xhrMonitor.pushContext(this.xhr_); + try { + return goog.net.XhrIo.superClass_.dispatchEvent.call(this, e) + }finally { + goog.net.xhrMonitor.popContext() + } + }else { + return goog.net.XhrIo.superClass_.dispatchEvent.call(this, e) + } +}; +a.timeout_ = function() { + if(typeof goog != "undefined") { + if(this.xhr_) { + this.lastError_ = "Timed out after " + this.timeoutInterval_ + "ms, aborting"; + this.lastErrorCode_ = goog.net.ErrorCode.TIMEOUT; + this.logger_.fine(this.formatMsg_(this.lastError_)); + this.dispatchEvent(goog.net.EventType.TIMEOUT); + this.abort(goog.net.ErrorCode.TIMEOUT) + } + } +}; +a.error_ = function(errorCode, err) { + this.active_ = false; + if(this.xhr_) { + this.inAbort_ = true; + this.xhr_.abort(); + this.inAbort_ = false + } + this.lastError_ = err; + this.lastErrorCode_ = errorCode; + this.dispatchErrors_(); + this.cleanUpXhr_() +}; +a.dispatchErrors_ = function() { + if(!this.errorDispatched_) { + this.errorDispatched_ = true; + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.ERROR) + } +}; +a.abort = function(opt_failureCode) { + if(this.xhr_) { + this.logger_.fine(this.formatMsg_("Aborting")); + this.active_ = false; + this.inAbort_ = true; + this.xhr_.abort(); + this.inAbort_ = false; + this.lastErrorCode_ = opt_failureCode || goog.net.ErrorCode.ABORT; + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.ABORT); + this.cleanUpXhr_() + } +}; +a.disposeInternal = function() { + if(this.xhr_) { + if(this.active_) { + this.active_ = false; + this.inAbort_ = true; + this.xhr_.abort(); + this.inAbort_ = false + } + this.cleanUpXhr_(true) + } + goog.net.XhrIo.superClass_.disposeInternal.call(this) +}; +a.onReadyStateChange_ = function() { + !this.inOpen_ && !this.inSend_ && !this.inAbort_ ? this.onReadyStateChangeEntryPoint_() : this.onReadyStateChangeHelper_() +}; +a.onReadyStateChangeEntryPoint_ = function() { + this.onReadyStateChangeHelper_() +}; +a.onReadyStateChangeHelper_ = function() { + if(this.active_) { + if(typeof goog != "undefined") { + if(this.xhrOptions_[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] && this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE && this.getStatus() == 2) { + this.logger_.fine(this.formatMsg_("Local request error detected and ignored")) + }else { + if(this.inSend_ && this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE) { + goog.Timer.defaultTimerObject.setTimeout(goog.bind(this.onReadyStateChange_, this), 0) + }else { + this.dispatchEvent(goog.net.EventType.READY_STATE_CHANGE); + if(this.isComplete()) { + this.logger_.fine(this.formatMsg_("Request complete")); + this.active_ = false; + if(this.isSuccess()) { + this.dispatchEvent(goog.net.EventType.COMPLETE); + this.dispatchEvent(goog.net.EventType.SUCCESS) + }else { + this.lastErrorCode_ = goog.net.ErrorCode.HTTP_ERROR; + this.lastError_ = this.getStatusText() + " [" + this.getStatus() + "]"; + this.dispatchErrors_() + } + this.cleanUpXhr_() + } + } + } + } + } +}; +a.cleanUpXhr_ = function(opt_fromDispose) { + if(this.xhr_) { + var xhr = this.xhr_, clearedOnReadyStateChange = this.xhrOptions_[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] ? goog.nullFunction : null; + this.xhrOptions_ = this.xhr_ = null; + if(this.timeoutId_) { + goog.Timer.defaultTimerObject.clearTimeout(this.timeoutId_); + this.timeoutId_ = null + } + if(!opt_fromDispose) { + goog.net.xhrMonitor.pushContext(xhr); + this.dispatchEvent(goog.net.EventType.READY); + goog.net.xhrMonitor.popContext() + } + goog.net.xhrMonitor.markXhrClosed(xhr); + try { + xhr.onreadystatechange = clearedOnReadyStateChange + }catch(e) { + this.logger_.severe("Problem encountered resetting onreadystatechange: " + e.message) + } + } +}; +a.isComplete = function() { + return this.getReadyState() == goog.net.XmlHttp.ReadyState.COMPLETE +}; +a.isSuccess = function() { + switch(this.getStatus()) { + case 0: + ; + case 200: + ; + case 204: + ; + case 304: + return true; + default: + return false + } +}; +a.getReadyState = function() { + return this.xhr_ ? this.xhr_.readyState : goog.net.XmlHttp.ReadyState.UNINITIALIZED +}; +a.getStatus = function() { + try { + return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? this.xhr_.status : -1 + }catch(e) { + this.logger_.warning("Can not get status: " + e.message); + return-1 + } +}; +a.getStatusText = function() { + try { + return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? this.xhr_.statusText : "" + }catch(e) { + this.logger_.fine("Can not get status: " + e.message); + return"" + } +}; +a.getResponseText = function() { + return this.xhr_ ? this.xhr_.responseText : "" +}; +a.getResponseHeader = function(key) { + return this.xhr_ && this.isComplete() ? this.xhr_.getResponseHeader(key) : undefined +}; +a.formatMsg_ = function(msg) { + return msg + " [" + this.lastMethod_ + " " + this.lastUri_ + " " + this.getStatus() + "]" +};goog.uri = {}; +goog.uri.utils = {}; +goog.uri.utils.CharCode_ = {AMPERSAND:38, EQUAL:61, HASH:35, QUESTION:63}; +goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) { + var out = []; + opt_scheme && out.push(opt_scheme, ":"); + if(opt_domain) { + out.push("//"); + opt_userInfo && out.push(opt_userInfo, "@"); + out.push(opt_domain); + opt_port && out.push(":", opt_port) + } + opt_path && out.push(opt_path); + opt_queryData && out.push("?", opt_queryData); + opt_fragment && out.push("#", opt_fragment); + return out.join("") +}; +goog.uri.utils.splitRe_ = RegExp("^(?:([^:/?#.]+):)?(?://(?:([^/?#]*)@)?([\\w\\d\\-\\u0100-\\uffff.%]*)(?::([0-9]+))?)?([^?#]+)?(?:\\?([^#]*))?(?:#(.*))?$"); +goog.uri.utils.ComponentIndex = {SCHEME:1, USER_INFO:2, DOMAIN:3, PORT:4, PATH:5, QUERY_DATA:6, FRAGMENT:7}; +goog.uri.utils.split = function(uri) { + return uri.match(goog.uri.utils.splitRe_) +}; +goog.uri.utils.decodeIfPossible_ = function(uri) { + return uri && decodeURIComponent(uri) +}; +goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) { + return goog.uri.utils.split(uri)[componentIndex] || null +}; +goog.uri.utils.getScheme = function(uri) { + return goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.SCHEME, uri) +}; +goog.uri.utils.getUserInfoEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.USER_INFO, uri) +}; +goog.uri.utils.getUserInfo = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getUserInfoEncoded(uri)) +}; +goog.uri.utils.getDomainEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.DOMAIN, uri) +}; +goog.uri.utils.getDomain = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getDomainEncoded(uri)) +}; +goog.uri.utils.getPort = function(uri) { + return Number(goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.PORT, uri)) || null +}; +goog.uri.utils.getPathEncoded = function(uri) { + return goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.PATH, uri) +}; +goog.uri.utils.getPath = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getPathEncoded(uri)) +}; +goog.uri.utils.getQueryData = function(uri) { + return goog.uri.utils.getComponentByIndex_(goog.uri.utils.ComponentIndex.QUERY_DATA, uri) +}; +goog.uri.utils.getFragmentEncoded = function(uri) { + var hashIndex = uri.indexOf("#"); + return hashIndex < 0 ? null : uri.substr(hashIndex + 1) +}; +goog.uri.utils.setFragmentEncoded = function(uri, fragment) { + return goog.uri.utils.removeFragment(uri) + (fragment ? "#" + fragment : "") +}; +goog.uri.utils.getFragment = function(uri) { + return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getFragmentEncoded(uri)) +}; +goog.uri.utils.getHost = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts(pieces[goog.uri.utils.ComponentIndex.SCHEME], pieces[goog.uri.utils.ComponentIndex.USER_INFO], pieces[goog.uri.utils.ComponentIndex.DOMAIN], pieces[goog.uri.utils.ComponentIndex.PORT]) +}; +goog.uri.utils.getPathAndAfter = function(uri) { + var pieces = goog.uri.utils.split(uri); + return goog.uri.utils.buildFromEncodedParts(null, null, null, null, pieces[goog.uri.utils.ComponentIndex.PATH], pieces[goog.uri.utils.ComponentIndex.QUERY_DATA], pieces[goog.uri.utils.ComponentIndex.FRAGMENT]) +}; +goog.uri.utils.removeFragment = function(uri) { + var hashIndex = uri.indexOf("#"); + return hashIndex < 0 ? uri : uri.substr(0, hashIndex) +}; +goog.uri.utils.haveSameDomain = function(uri1, uri2) { + var pieces1 = goog.uri.utils.split(uri1), pieces2 = goog.uri.utils.split(uri2); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && pieces1[goog.uri.utils.ComponentIndex.SCHEME] == pieces2[goog.uri.utils.ComponentIndex.SCHEME] && pieces1[goog.uri.utils.ComponentIndex.PORT] == pieces2[goog.uri.utils.ComponentIndex.PORT] +}; +goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) { + if(goog.DEBUG && (uri.indexOf("#") >= 0 || uri.indexOf("?") >= 0)) { + throw Error("goog.uri.utils: Fragment or query identifiers are not supported: [" + uri + "]"); + } +}; +goog.uri.utils.appendQueryData_ = function(buffer) { + if(buffer[1]) { + var baseUri = buffer[0], hashIndex = baseUri.indexOf("#"); + if(hashIndex >= 0) { + buffer.push(baseUri.substr(hashIndex)); + buffer[0] = baseUri = baseUri.substr(0, hashIndex) + } + var questionIndex = baseUri.indexOf("?"); + if(questionIndex < 0) { + buffer[1] = "?" + }else { + if(questionIndex == baseUri.length - 1) { + buffer[1] = undefined + } + } + } + return buffer.join("") +}; +goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) { + if(goog.isArray(value)) { + for(var j = 0;j < value.length;j++) { + pairs.push("&", key); + value[j] !== "" && pairs.push("=", goog.string.urlEncode(value[j])) + } + }else { + if(value != null) { + pairs.push("&", key); + value !== "" && pairs.push("=", goog.string.urlEncode(value)) + } + } +}; +goog.uri.utils.buildQueryDataBuffer_ = function(buffer, keysAndValues, opt_startIndex) { + goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0), 0) % 2 == 0, "goog.uri.utils: Key/value lists must be even in length."); + for(var i = opt_startIndex || 0;i < keysAndValues.length;i += 2) { + goog.uri.utils.appendKeyValuePairs_(keysAndValues[i], keysAndValues[i + 1], buffer) + } + return buffer +}; +goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) { + var buffer = goog.uri.utils.buildQueryDataBuffer_([], keysAndValues, opt_startIndex); + buffer[0] = ""; + return buffer.join("") +}; +goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) { + for(var key in map) { + goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer) + } + return buffer +}; +goog.uri.utils.buildQueryDataFromMap = function(map) { + var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map); + buffer[0] = ""; + return buffer.join("") +}; +goog.uri.utils.appendParams = function(uri) { + return goog.uri.utils.appendQueryData_(arguments.length == 2 ? goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) : goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1)) +}; +goog.uri.utils.appendParamsFromMap = function(uri, map) { + return goog.uri.utils.appendQueryData_(goog.uri.utils.buildQueryDataBufferFromMap_([uri], map)) +}; +goog.uri.utils.appendParam = function(uri, key, value) { + return goog.uri.utils.appendQueryData_([uri, "&", key, "=", goog.string.urlEncode(value)]) +}; +goog.uri.utils.findParam_ = function(uri, startIndex, keyEncoded, hashOrEndIndex) { + var index = startIndex; + for(var keyLength = keyEncoded.length;(index = uri.indexOf(keyEncoded, index)) >= 0 && index < hashOrEndIndex;) { + var precedingChar = uri.charCodeAt(index - 1); + if(precedingChar == goog.uri.utils.CharCode_.AMPERSAND || precedingChar == goog.uri.utils.CharCode_.QUESTION) { + var followingChar = uri.charCodeAt(index + keyLength); + if(!followingChar || followingChar == goog.uri.utils.CharCode_.EQUAL || followingChar == goog.uri.utils.CharCode_.AMPERSAND || followingChar == goog.uri.utils.CharCode_.HASH) { + return index + } + } + index += keyLength + 1 + } + return-1 +}; +goog.uri.utils.hashOrEndRe_ = /#|$/; +goog.uri.utils.hasParam = function(uri, keyEncoded) { + return goog.uri.utils.findParam_(uri, 0, keyEncoded, uri.search(goog.uri.utils.hashOrEndRe_)) >= 0 +}; +goog.uri.utils.getParamValue = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_), foundIndex = goog.uri.utils.findParam_(uri, 0, keyEncoded, hashOrEndIndex); + if(foundIndex < 0) { + return null + }else { + var endPosition = uri.indexOf("&", foundIndex); + if(endPosition < 0 || endPosition > hashOrEndIndex) { + endPosition = hashOrEndIndex + } + foundIndex += keyEncoded.length + 1; + return goog.string.urlDecode(uri.substr(foundIndex, endPosition - foundIndex)) + } +}; +goog.uri.utils.getParamValues = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_), position = 0, foundIndex; + for(var result = [];(foundIndex = goog.uri.utils.findParam_(uri, position, keyEncoded, hashOrEndIndex)) >= 0;) { + position = uri.indexOf("&", foundIndex); + if(position < 0 || position > hashOrEndIndex) { + position = hashOrEndIndex + } + foundIndex += keyEncoded.length + 1; + result.push(goog.string.urlDecode(uri.substr(foundIndex, position - foundIndex))) + } + return result +}; +goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/; +goog.uri.utils.removeParam = function(uri, keyEncoded) { + var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_), position = 0, foundIndex; + for(var buffer = [];(foundIndex = goog.uri.utils.findParam_(uri, position, keyEncoded, hashOrEndIndex)) >= 0;) { + buffer.push(uri.substring(position, foundIndex)); + position = Math.min(uri.indexOf("&", foundIndex) + 1 || hashOrEndIndex, hashOrEndIndex) + } + buffer.push(uri.substr(position)); + return buffer.join("").replace(goog.uri.utils.trailingQueryPunctuationRe_, "$1") +}; +goog.uri.utils.setParam = function(uri, keyEncoded, value) { + return goog.uri.utils.appendParam(goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value) +}; +goog.uri.utils.appendPath = function(baseUri, path) { + goog.uri.utils.assertNoFragmentsOrQueries_(baseUri); + if(goog.string.endsWith(baseUri, "/")) { + baseUri = baseUri.substr(0, baseUri.length - 1) + } + if(goog.string.startsWith(path, "/")) { + path = path.substr(1) + } + return goog.string.buildString(baseUri, "/", path) +}; +goog.uri.utils.StandardQueryParam = {RANDOM:"zx"}; +goog.uri.utils.makeUnique = function(uri) { + return goog.uri.utils.setParam(uri, goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString()) +};goog.Uri = function(opt_uri, opt_ignoreCase) { + var m; + if(opt_uri instanceof goog.Uri) { + this.setIgnoreCase(opt_ignoreCase == null ? opt_uri.getIgnoreCase() : opt_ignoreCase); + this.setScheme(opt_uri.getScheme()); + this.setUserInfo(opt_uri.getUserInfo()); + this.setDomain(opt_uri.getDomain()); + this.setPort(opt_uri.getPort()); + this.setPath(opt_uri.getPath()); + this.setQueryData(opt_uri.getQueryData().clone()); + this.setFragment(opt_uri.getFragment()) + }else { + if(opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) { + this.setIgnoreCase(!!opt_ignoreCase); + this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || "", true); + this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || "", true); + this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || "", true); + this.setPort(m[goog.uri.utils.ComponentIndex.PORT]); + this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || "", true); + this.setQuery(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || "", true); + this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || "", true) + }else { + this.setIgnoreCase(!!opt_ignoreCase); + this.queryData_ = new goog.Uri.QueryData(null, this, this.ignoreCase_) + } + } +}; +goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM; +a = goog.Uri.prototype; +a.scheme_ = ""; +a.userInfo_ = ""; +a.domain_ = ""; +a.port_ = null; +a.path_ = ""; +a.fragment_ = ""; +a.isReadOnly_ = false; +a.ignoreCase_ = false; +a.toString = function() { + if(this.cachedToString_) { + return this.cachedToString_ + } + var out = []; + this.scheme_ && out.push(goog.Uri.encodeSpecialChars_(this.scheme_, goog.Uri.reDisallowedInSchemeOrUserInfo_), ":"); + if(this.domain_) { + out.push("//"); + this.userInfo_ && out.push(goog.Uri.encodeSpecialChars_(this.userInfo_, goog.Uri.reDisallowedInSchemeOrUserInfo_), "@"); + out.push(goog.Uri.encodeString_(this.domain_)); + this.port_ != null && out.push(":", String(this.getPort())) + } + this.path_ && out.push(goog.Uri.encodeSpecialChars_(this.path_, goog.Uri.reDisallowedInPath_)); + var query = String(this.queryData_); + query && out.push("?", query); + this.fragment_ && out.push("#", goog.Uri.encodeSpecialChars_(this.fragment_, goog.Uri.reDisallowedInFragment_)); + return this.cachedToString_ = out.join("") +}; +a.resolve = function(relativeUri) { + var absoluteUri = this.clone(), overridden = relativeUri.hasScheme(); + if(overridden) { + absoluteUri.setScheme(relativeUri.getScheme()) + }else { + overridden = relativeUri.hasUserInfo() + } + if(overridden) { + absoluteUri.setUserInfo(relativeUri.getUserInfo()) + }else { + overridden = relativeUri.hasDomain() + } + if(overridden) { + absoluteUri.setDomain(relativeUri.getDomain()) + }else { + overridden = relativeUri.hasPort() + } + var path = relativeUri.getPath(); + if(overridden) { + absoluteUri.setPort(relativeUri.getPort()) + }else { + if(overridden = relativeUri.hasPath()) { + if(path.charAt(0) != "/") { + if(this.hasDomain() && !this.hasPath()) { + path = "/" + path + }else { + var lastSlashIndex = absoluteUri.getPath().lastIndexOf("/"); + if(lastSlashIndex != -1) { + path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path + } + } + } + path = goog.Uri.removeDotSegments(path) + } + } + if(overridden) { + absoluteUri.setPath(path) + }else { + overridden = relativeUri.hasQuery() + } + if(overridden) { + absoluteUri.setQuery(relativeUri.getDecodedQuery()) + }else { + overridden = relativeUri.hasFragment() + } + overridden && absoluteUri.setFragment(relativeUri.getFragment()); + return absoluteUri +}; +a.clone = function() { + return goog.Uri.create(this.scheme_, this.userInfo_, this.domain_, this.port_, this.path_, this.queryData_.clone(), this.fragment_, this.ignoreCase_) +}; +a.getScheme = function() { + return this.scheme_ +}; +a.setScheme = function(newScheme, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + if(this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme) : newScheme) { + this.scheme_ = this.scheme_.replace(/:$/, "") + } + return this +}; +a.hasScheme = function() { + return!!this.scheme_ +}; +a.getUserInfo = function() { + return this.userInfo_ +}; +a.setUserInfo = function(newUserInfo, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) : newUserInfo; + return this +}; +a.hasUserInfo = function() { + return!!this.userInfo_ +}; +a.getDomain = function() { + return this.domain_ +}; +a.setDomain = function(newDomain, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain) : newDomain; + return this +}; +a.hasDomain = function() { + return!!this.domain_ +}; +a.getPort = function() { + return this.port_ +}; +a.setPort = function(newPort) { + this.enforceReadOnly(); + delete this.cachedToString_; + if(newPort) { + newPort = Number(newPort); + if(isNaN(newPort) || newPort < 0) { + throw Error("Bad port number " + newPort); + } + this.port_ = newPort + }else { + this.port_ = null + } + return this +}; +a.hasPort = function() { + return this.port_ != null +}; +a.getPath = function() { + return this.path_ +}; +a.setPath = function(newPath, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath) : newPath; + return this +}; +a.hasPath = function() { + return!!this.path_ +}; +a.hasQuery = function() { + return this.queryData_.toString() !== "" +}; +a.setQueryData = function(queryData, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + if(queryData instanceof goog.Uri.QueryData) { + this.queryData_ = queryData; + this.queryData_.uri_ = this; + this.queryData_.setIgnoreCase(this.ignoreCase_) + }else { + opt_decode || (queryData = goog.Uri.encodeSpecialChars_(queryData, goog.Uri.reDisallowedInQuery_)); + this.queryData_ = new goog.Uri.QueryData(queryData, this, this.ignoreCase_) + } + return this +}; +a.setQuery = function(newQuery, opt_decode) { + return this.setQueryData(newQuery, opt_decode) +}; +a.getDecodedQuery = function() { + return this.queryData_.toDecodedString() +}; +a.getQueryData = function() { + return this.queryData_ +}; +a.setParameterValue = function(key, value) { + this.enforceReadOnly(); + delete this.cachedToString_; + this.queryData_.set(key, value); + return this +}; +a.getFragment = function() { + return this.fragment_ +}; +a.setFragment = function(newFragment, opt_decode) { + this.enforceReadOnly(); + delete this.cachedToString_; + this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) : newFragment; + return this +}; +a.hasFragment = function() { + return!!this.fragment_ +}; +a.makeUnique = function() { + this.enforceReadOnly(); + this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString()); + return this +}; +a.removeParameter = function(key) { + this.enforceReadOnly(); + this.queryData_.remove(key); + return this +}; +a.enforceReadOnly = function() { + if(this.isReadOnly_) { + throw Error("Tried to modify a read-only Uri"); + } +}; +a.setIgnoreCase = function(ignoreCase) { + this.ignoreCase_ = ignoreCase; + this.queryData_ && this.queryData_.setIgnoreCase(ignoreCase) +}; +a.getIgnoreCase = function() { + return this.ignoreCase_ +}; +goog.Uri.parse = function(uri, opt_ignoreCase) { + return uri instanceof goog.Uri ? uri.clone() : new goog.Uri(uri, opt_ignoreCase) +}; +goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query, opt_fragment, opt_ignoreCase) { + var uri = new goog.Uri(null, opt_ignoreCase); + opt_scheme && uri.setScheme(opt_scheme); + opt_userInfo && uri.setUserInfo(opt_userInfo); + opt_domain && uri.setDomain(opt_domain); + opt_port && uri.setPort(opt_port); + opt_path && uri.setPath(opt_path); + opt_query && uri.setQueryData(opt_query); + opt_fragment && uri.setFragment(opt_fragment); + return uri +}; +goog.Uri.resolve = function(base, rel) { + base instanceof goog.Uri || (base = goog.Uri.parse(base)); + rel instanceof goog.Uri || (rel = goog.Uri.parse(rel)); + return base.resolve(rel) +}; +goog.Uri.removeDotSegments = function(path) { + if(path == ".." || path == ".") { + return"" + }else { + if(!goog.string.contains(path, "./") && !goog.string.contains(path, "/.")) { + return path + }else { + var leadingSlash = goog.string.startsWith(path, "/"), segments = path.split("/"), out = []; + for(var pos = 0;pos < segments.length;) { + var segment = segments[pos++]; + if(segment == ".") { + leadingSlash && pos == segments.length && out.push("") + }else { + if(segment == "..") { + if(out.length > 1 || out.length == 1 && out[0] != "") { + out.pop() + } + leadingSlash && pos == segments.length && out.push("") + }else { + out.push(segment); + leadingSlash = true + } + } + } + return out.join("/") + } + } +}; +goog.Uri.decodeOrEmpty_ = function(val) { + return val ? decodeURIComponent(val) : "" +}; +goog.Uri.encodeString_ = function(unescapedPart) { + if(goog.isString(unescapedPart)) { + return encodeURIComponent(unescapedPart) + } + return null +}; +goog.Uri.encodeSpecialRegExp_ = /^[a-zA-Z0-9\-_.!~*'():\/;?]*$/; +goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra) { + var ret = null; + if(goog.isString(unescapedPart)) { + ret = unescapedPart; + goog.Uri.encodeSpecialRegExp_.test(ret) || (ret = encodeURI(unescapedPart)); + if(ret.search(extra) >= 0) { + ret = ret.replace(extra, goog.Uri.encodeChar_) + } + } + return ret +}; +goog.Uri.encodeChar_ = function(ch) { + var n = ch.charCodeAt(0); + return"%" + (n >> 4 & 15).toString(16) + (n & 15).toString(16) +}; +goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g; +goog.Uri.reDisallowedInPath_ = /[\#\?]/g; +goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g; +goog.Uri.reDisallowedInFragment_ = /#/g; +goog.Uri.haveSameDomain = function(uri1String, uri2String) { + var pieces1 = goog.uri.utils.split(uri1String), pieces2 = goog.uri.utils.split(uri2String); + return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] == pieces2[goog.uri.utils.ComponentIndex.DOMAIN] && pieces1[goog.uri.utils.ComponentIndex.PORT] == pieces2[goog.uri.utils.ComponentIndex.PORT] +}; +goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) { + this.encodedQuery_ = opt_query || null; + this.uri_ = opt_uri || null; + this.ignoreCase_ = !!opt_ignoreCase +}; +goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() { + if(!this.keyMap_) { + this.keyMap_ = new goog.structs.Map; + if(this.encodedQuery_) { + var pairs = this.encodedQuery_.split("&"); + for(var i = 0;i < pairs.length;i++) { + var indexOfEquals = pairs[i].indexOf("="), name = null, value = null; + if(indexOfEquals >= 0) { + name = pairs[i].substring(0, indexOfEquals); + value = pairs[i].substring(indexOfEquals + 1) + }else { + name = pairs[i] + } + name = goog.string.urlDecode(name); + name = this.getKeyName_(name); + this.add(name, value ? goog.string.urlDecode(value) : "") + } + } + } +}; +goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) { + var keys = goog.structs.getKeys(map); + if(typeof keys == "undefined") { + throw Error("Keys are undefined"); + } + return goog.Uri.QueryData.createFromKeysValues(keys, goog.structs.getValues(map), opt_uri, opt_ignoreCase) +}; +goog.Uri.QueryData.createFromKeysValues = function(keys, values, opt_uri, opt_ignoreCase) { + if(keys.length != values.length) { + throw Error("Mismatched lengths for keys/values"); + } + var queryData = new goog.Uri.QueryData(null, opt_uri, opt_ignoreCase); + for(var i = 0;i < keys.length;i++) { + queryData.add(keys[i], values[i]) + } + return queryData +}; +goog.Uri.QueryData.prototype.keyMap_ = null; +goog.Uri.QueryData.prototype.count_ = null; +goog.Uri.QueryData.decodedQuery_ = null; +a = goog.Uri.QueryData.prototype; +a.getCount = function() { + this.ensureKeyMapInitialized_(); + return this.count_ +}; +a.add = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + key = this.getKeyName_(key); + if(this.containsKey(key)) { + var current = this.keyMap_.get(key); + goog.isArray(current) ? current.push(value) : this.keyMap_.set(key, [current, value]) + }else { + this.keyMap_.set(key, value) + } + this.count_++; + return this +}; +a.remove = function(key) { + this.ensureKeyMapInitialized_(); + key = this.getKeyName_(key); + if(this.keyMap_.containsKey(key)) { + this.invalidateCache_(); + var old = this.keyMap_.get(key); + if(goog.isArray(old)) { + this.count_ -= old.length + }else { + this.count_-- + } + return this.keyMap_.remove(key) + } + return false +}; +a.clear = function() { + this.invalidateCache_(); + this.keyMap_ && this.keyMap_.clear(); + this.count_ = 0 +}; +a.isEmpty = function() { + this.ensureKeyMapInitialized_(); + return this.count_ == 0 +}; +a.containsKey = function(key) { + this.ensureKeyMapInitialized_(); + key = this.getKeyName_(key); + return this.keyMap_.containsKey(key) +}; +a.containsValue = function(value) { + var vals = this.getValues(); + return goog.array.contains(vals, value) +}; +a.getKeys = function() { + this.ensureKeyMapInitialized_(); + var vals = this.keyMap_.getValues(), keys = this.keyMap_.getKeys(), rv = []; + for(var i = 0;i < keys.length;i++) { + var val = vals[i]; + if(goog.isArray(val)) { + for(var j = 0;j < val.length;j++) { + rv.push(keys[i]) + } + }else { + rv.push(keys[i]) + } + } + return rv +}; +a.getValues = function(opt_key) { + this.ensureKeyMapInitialized_(); + var rv; + if(opt_key) { + var key = this.getKeyName_(opt_key); + if(this.containsKey(key)) { + var value = this.keyMap_.get(key); + if(goog.isArray(value)) { + return value + }else { + rv = []; + rv.push(value) + } + }else { + rv = [] + } + }else { + var vals = this.keyMap_.getValues(); + rv = []; + for(var i = 0;i < vals.length;i++) { + var val = vals[i]; + goog.isArray(val) ? goog.array.extend(rv, val) : rv.push(val) + } + } + return rv +}; +a.set = function(key, value) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + key = this.getKeyName_(key); + if(this.containsKey(key)) { + var old = this.keyMap_.get(key); + if(goog.isArray(old)) { + this.count_ -= old.length + }else { + this.count_-- + } + } + this.keyMap_.set(key, value); + this.count_++; + return this +}; +a.get = function(key, opt_default) { + this.ensureKeyMapInitialized_(); + key = this.getKeyName_(key); + if(this.containsKey(key)) { + var val = this.keyMap_.get(key); + return goog.isArray(val) ? val[0] : val + }else { + return opt_default + } +}; +a.toString = function() { + if(this.encodedQuery_) { + return this.encodedQuery_ + } + if(!this.keyMap_) { + return"" + } + var sb = [], count = 0, keys = this.keyMap_.getKeys(); + for(var i = 0;i < keys.length;i++) { + var key = keys[i], encodedKey = goog.string.urlEncode(key), val = this.keyMap_.get(key); + if(goog.isArray(val)) { + for(var j = 0;j < val.length;j++) { + count > 0 && sb.push("&"); + sb.push(encodedKey); + val[j] !== "" && sb.push("=", goog.string.urlEncode(val[j])); + count++ + } + }else { + count > 0 && sb.push("&"); + sb.push(encodedKey); + val !== "" && sb.push("=", goog.string.urlEncode(val)); + count++ + } + } + return this.encodedQuery_ = sb.join("") +}; +a.toDecodedString = function() { + if(!this.decodedQuery_) { + this.decodedQuery_ = goog.Uri.decodeOrEmpty_(this.toString()) + } + return this.decodedQuery_ +}; +a.invalidateCache_ = function() { + delete this.decodedQuery_; + delete this.encodedQuery_; + this.uri_ && delete this.uri_.cachedToString_ +}; +a.clone = function() { + var rv = new goog.Uri.QueryData; + if(this.decodedQuery_) { + rv.decodedQuery_ = this.decodedQuery_ + } + if(this.encodedQuery_) { + rv.encodedQuery_ = this.encodedQuery_ + } + if(this.keyMap_) { + rv.keyMap_ = this.keyMap_.clone() + } + return rv +}; +a.getKeyName_ = function(arg) { + var keyName = String(arg); + if(this.ignoreCase_) { + keyName = keyName.toLowerCase() + } + return keyName +}; +a.setIgnoreCase = function(ignoreCase) { + var resetKeys = ignoreCase && !this.ignoreCase_; + if(resetKeys) { + this.ensureKeyMapInitialized_(); + this.invalidateCache_(); + goog.structs.forEach(this.keyMap_, function(value, key) { + var lowerCase = key.toLowerCase(); + if(key != lowerCase) { + this.remove(key); + this.add(lowerCase, value) + } + }, this) + } + this.ignoreCase_ = ignoreCase +}; +a.extend = function() { + for(var i = 0;i < arguments.length;i++) { + var data = arguments[i]; + goog.structs.forEach(data, function(value, key) { + this.add(key, value) + }, this) + } +};goog.appengine = {}; +goog.appengine.DevChannel = function(channelId) { + this.channelId_ = channelId +}; +goog.appengine.DevChannel.prototype.open = function(opt_handler) { + opt_handler = opt_handler || new goog.appengine.DevSocket.Handler; + return new goog.appengine.DevSocket(this.channelId_, opt_handler) +}; +goog.appengine.DevSocket = function(channelId, handler) { + this.readyState = goog.appengine.DevSocket.ReadyState.CONNECTING; + this.channelId_ = channelId; + this.applicationKey_ = channelId.substring(channelId.lastIndexOf("-") + 1); + this.clientId_ = null; + this.onopen = handler.onopen; + this.onmessage = handler.onmessage; + this.onerror = handler.onerror; + this.onclose = handler.onclose; + this.doc_ = goog.dom.getDocument(); + this.win_ = goog.dom.getWindow(); + goog.net.XhrIo.send(this.getUrl_("connect"), goog.bind(this.connect_, this)); + goog.events.listen(this.win_, "beforeunload", goog.bind(this.beforeunload_, this)) +}; +goog.appengine.DevSocket.POLLING_TIMEOUT_MS = 500; +goog.appengine.DevSocket.BASE_URL = "/_ah/channel/"; +goog.appengine.DevSocket.ReadyState = {CONNECTING:0, OPEN:1, CLOSING:2, CLOSED:3}; +a = goog.appengine.DevSocket.prototype; +a.getUrl_ = function(command) { + var url = goog.appengine.DevSocket.BASE_URL + "dev?command=" + command + "&channel=" + this.channelId_; + if(this.clientId_) { + url += "&client=" + this.clientId_ + } + return url +}; +a.connect_ = function(e) { + var xhr = e.target; + if(xhr.isSuccess()) { + this.clientId_ = xhr.getResponseText(); + this.readyState = goog.appengine.DevSocket.ReadyState.OPEN; + this.onopen(); + this.win_.setTimeout(goog.bind(this.poll_, this), goog.appengine.DevSocket.POLLING_TIMEOUT_MS) + }else { + this.readyState = goog.appengine.DevSocket.ReadyState.CLOSING; + var evt = {}; + evt.description = xhr.getStatusText(); + evt.code = xhr.getStatus(); + this.onerror(evt); + this.readyState = goog.appengine.DevSocket.ReadyState.CLOSED; + this.onclose() + } +}; +a.disconnect_ = function() { + this.readyState = goog.appengine.DevSocket.ReadyState.CLOSED; + this.onclose() +}; +a.forwardMessage_ = function(e) { + var xhr = e.target; + if(xhr.isSuccess()) { + var evt = {}; + evt.data = xhr.getResponseText(); + evt.data.length && this.onmessage(evt); + this.win_.setTimeout(goog.bind(this.poll_, this), goog.appengine.DevSocket.POLLING_TIMEOUT_MS) + }else { + evt = {}; + evt.description = xhr.getStatusText(); + evt.code = xhr.getStatus(); + this.onerror(evt) + } +}; +a.poll_ = function() { + goog.net.XhrIo.send(this.getUrl_("poll"), goog.bind(this.forwardMessage_, this)) +}; +a.beforeunload_ = function() { + var xhr = new goog.net.XmlHttp; + xhr.open("GET", this.getUrl_("disconnect"), false); + xhr.send() +}; +a.forwardSendComplete_ = function(e) { + var xhr = e.target; + if(!xhr.isSuccess()) { + var evt = {}; + evt.description = xhr.getStatusText(); + evt.code = xhr.getStatus(); + this.onerror(evt) + } +}; +a.send = function(data) { + if(this.readyState != goog.appengine.DevSocket.ReadyState.OPEN) { + return false + } + var url = goog.appengine.DevSocket.BASE_URL + "receive", sendData = new goog.Uri.QueryData; + sendData.set("key", this.applicationKey_); + sendData.set("msg", data); + goog.net.XhrIo.send(url, goog.bind(this.forwardSendComplete_, this), "POST", sendData.toString()); + return true +}; +a.close = function() { + this.readyState = goog.appengine.DevSocket.ReadyState.CLOSING; + goog.net.XhrIo.send(this.getUrl_("disconnect"), goog.bind(this.disconnect_, this)) +}; +goog.appengine.DevSocket.Handler = function() { +}; +goog.appengine.DevSocket.Handler.prototype.onopen = function() { +}; +goog.appengine.DevSocket.Handler.prototype.onmessage = function() { +}; +goog.appengine.DevSocket.Handler.prototype.onerror = function() { +}; +goog.appengine.DevSocket.Handler.prototype.onclose = function() { +}; +goog.exportSymbol("goog.appengine.Channel", goog.appengine.DevChannel); +goog.exportSymbol("goog.appengine.Channel.prototype.open", goog.appengine.DevChannel.prototype.open); +goog.exportSymbol("goog.appengine.Socket.Handler", goog.appengine.DevSocket.Handler); +goog.exportSymbol("goog.appengine.Socket.Handler.prototype.onopen", goog.appengine.DevChannel.prototype.onopen); +goog.exportSymbol("goog.appengine.Socket.Handler.prototype.onmessage", goog.appengine.DevSocket.Handler.prototype.onmessage); +goog.exportSymbol("goog.appengine.Socket.Handler.prototype.onerror", goog.appengine.DevSocket.Handler.prototype.onerror); +goog.exportSymbol("goog.appengine.Socket.Handler.prototype.onclose", goog.appengine.DevSocket.Handler.prototype.onclose); +goog.exportSymbol("goog.appengine.Socket", goog.appengine.DevSocket); +goog.exportSymbol("goog.appengine.Socket.ReadyState", goog.appengine.DevSocket.ReadyState); +goog.exportSymbol("goog.appengine.Socket.prototype.send", goog.appengine.DevSocket.prototype.send); +goog.exportSymbol("goog.appengine.Socket.prototype.close", goog.appengine.DevSocket.prototype.close); })() diff --git a/google_appengine/google/appengine/tools/dev_appserver.py b/google_appengine/google/appengine/tools/dev_appserver.py new file mode 100755 index 0000000..c492ebc --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver.py @@ -0,0 +1,3892 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Pure-Python application server for testing applications locally. + +Given a port and the paths to a valid application directory (with an 'app.yaml' +file), the external library directory, and a relative URL to use for logins, +creates an HTTP server that can be used to test an application locally. Uses +stubs instead of actual APIs when SetupStubs() is called first. + +Example: + root_path = '/path/to/application/directory' + login_url = '/login' + port = 8080 + template_dir = '/path/to/appserver/templates' + server = dev_appserver.CreateServer(root_path, login_url, port, template_dir) + server.serve_forever() +""" + + +from google.appengine.tools import os_compat + +import __builtin__ +import BaseHTTPServer +import Cookie +import base64 +import cStringIO +import cgi +import cgitb + +try: + import distutils.util +except ImportError: + pass + +import dummy_thread +import email.Utils +import errno +import heapq +import httplib +import imp +import inspect +import itertools +import locale +import logging +import mimetools +import mimetypes +import os +import pickle +import pprint +import random +import select +import shutil +import tempfile + +import re +import sre_compile +import sre_constants +import sre_parse + +import socket +import sys +import time +import traceback +import types +import urlparse +import urllib + +import google +from google.pyglib import gexcept + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import appinfo +from google.appengine.api import blobstore +from google.appengine.api import croninfo +from google.appengine.api import datastore_admin +from google.appengine.api import datastore_file_stub +from google.appengine.api import mail +from google.appengine.api import mail_stub +from google.appengine.api import urlfetch_stub +from google.appengine.api import user_service_stub +from google.appengine.api import yaml_errors +from google.appengine.api.blobstore import blobstore_stub +from google.appengine.api.blobstore import file_blob_storage +from google.appengine.api.capabilities import capability_stub +from google.appengine.api.channel import channel_service_stub +from google.appengine.api.labs.taskqueue import taskqueue_stub +from google.appengine.api.memcache import memcache_stub +from google.appengine.api.xmpp import xmpp_service_stub +from google.appengine.datastore import datastore_sqlite_stub + +from google.appengine import dist + +from google.appengine.tools import dev_appserver_blobstore +from google.appengine.tools import dev_appserver_channel +from google.appengine.tools import dev_appserver_blobimage +from google.appengine.tools import dev_appserver_index +from google.appengine.tools import dev_appserver_login +from google.appengine.tools import dev_appserver_oauth +from google.appengine.tools import dev_appserver_upload + + +PYTHON_LIB_VAR = '$PYTHON_LIB' +DEVEL_CONSOLE_PATH = PYTHON_LIB_VAR + '/google/appengine/ext/admin' + +FILE_MISSING_EXCEPTIONS = frozenset([errno.ENOENT, errno.ENOTDIR]) + +MAX_URL_LENGTH = 2047 + +HEADER_TEMPLATE = 'logging_console_header.html' +SCRIPT_TEMPLATE = 'logging_console.js' +MIDDLE_TEMPLATE = 'logging_console_middle.html' +FOOTER_TEMPLATE = 'logging_console_footer.html' + +DEFAULT_ENV = { + 'GATEWAY_INTERFACE': 'CGI/1.1', + 'AUTH_DOMAIN': 'gmail.com', + 'USER_ORGANIZATION': '', + 'TZ': 'UTC', +} + +DEFAULT_SELECT_DELAY = 30.0 + +for ext, mime_type in mail.EXTENSION_MIME_MAP.iteritems(): + mimetypes.add_type(mime_type, '.' + ext) + +MAX_RUNTIME_RESPONSE_SIZE = 10 << 20 + +MAX_REQUEST_SIZE = 10 * 1024 * 1024 + +COPY_BLOCK_SIZE = 1 << 20 + +API_VERSION = '1' + +SITE_PACKAGES = os.path.normcase(os.path.join(os.path.dirname(os.__file__), + 'site-packages')) + +DEVEL_PAYLOAD_HEADER = 'HTTP_X_APPENGINE_DEVELOPMENT_PAYLOAD' +DEVEL_PAYLOAD_RAW_HEADER = 'X-AppEngine-Development-Payload' + + + +class Error(Exception): + """Base-class for exceptions in this module.""" + + +class InvalidAppConfigError(Error): + """The supplied application configuration file is invalid.""" + + +class AppConfigNotFoundError(Error): + """Application configuration file not found.""" + + +class TemplatesNotLoadedError(Error): + """Templates for the debugging console were not loaded.""" + + + +def SplitURL(relative_url): + """Splits a relative URL into its path and query-string components. + + Args: + relative_url: String containing the relative URL (often starting with '/') + to split. Should be properly escaped as www-form-urlencoded data. + + Returns: + Tuple (script_name, query_string) where: + script_name: Relative URL of the script that was accessed. + query_string: String containing everything after the '?' character. + """ + (unused_scheme, unused_netloc, path, query, + unused_fragment) = urlparse.urlsplit(relative_url) + return path, query + + +def GetFullURL(server_name, server_port, relative_url): + """Returns the full, original URL used to access the relative URL. + + Args: + server_name: Name of the local host, or the value of the 'host' header + from the request. + server_port: Port on which the request was served (string or int). + relative_url: Relative URL that was accessed, including query string. + + Returns: + String containing the original URL. + """ + if str(server_port) != '80': + netloc = '%s:%s' % (server_name, server_port) + else: + netloc = server_name + return 'http://%s%s' % (netloc, relative_url) + +def CopyStreamPart(source, destination, content_size): + """Copy a portion of a stream from one file-like object to another. + + Args: + source: Source stream to copy from. + destination: Destination stream to copy to. + content_size: Maximum bytes to copy. + + Returns: + Number of bytes actually copied. + """ + bytes_copied = 0 + bytes_left = content_size + while bytes_left > 0: + bytes = source.read(min(bytes_left, COPY_BLOCK_SIZE)) + bytes_read = len(bytes) + if bytes_read == 0: + break + destination.write(bytes) + bytes_copied += bytes_read + bytes_left -= bytes_read + return bytes_copied + + +class AppServerRequest(object): + """Encapsulates app-server request. + + Object used to hold a full appserver request. Used as a container that is + passed through the request forward chain and ultimately sent to the + URLDispatcher instances. + + Attributes: + relative_url: String containing the URL accessed. + path: Local path of the resource that was matched; back-references will be + replaced by values matched in the relative_url. Path may be relative + or absolute, depending on the resource being served (e.g., static files + will have an absolute path; scripts will be relative). + headers: Instance of mimetools.Message with headers from the request. + infile: File-like object with input data from the request. + force_admin: Allow request admin-only URLs to proceed regardless of whether + user is logged in or is an admin. + """ + + ATTRIBUTES = ['relative_url', + 'path', + 'headers', + 'infile', + 'force_admin', + ] + + def __init__(self, + relative_url, + path, + headers, + infile, + force_admin=False): + """Constructor. + + Args: + relative_url: Mapped directly to attribute. + path: Mapped directly to attribute. + headers: Mapped directly to attribute. + infile: Mapped directly to attribute. + force_admin: Mapped directly to attribute. + """ + self.relative_url = relative_url + self.path = path + self.headers = headers + self.infile = infile + self.force_admin = force_admin + if DEVEL_PAYLOAD_RAW_HEADER in self.headers: + self.force_admin = True + + def __eq__(self, other): + """Used mainly for testing. + + Returns: + True if all fields of both requests are equal, else False. + """ + if type(self) == type(other): + for attribute in self.ATTRIBUTES: + if getattr(self, attribute) != getattr(other, attribute): + return False + return True + + def __repr__(self): + """String representation of request. + + Used mainly for testing. + + Returns: + String representation of AppServerRequest. Strings of different + request objects that have the same values for all fields compare + as equal. + """ + results = [] + for attribute in self.ATTRUBUTES: + results.append('%s: %s' % (attributes, getattr(self, attributes))) + return '' % ' '.join(results) + + +class URLDispatcher(object): + """Base-class for handling HTTP requests.""" + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Dispatch and handle an HTTP request. + + base_env_dict should contain at least these CGI variables: + REQUEST_METHOD, REMOTE_ADDR, SERVER_SOFTWARE, SERVER_NAME, + SERVER_PROTOCOL, SERVER_PORT + + Args: + request: AppServerRequest instance. + outfile: File-like object where output data should be written. + base_env_dict: Dictionary of CGI environment parameters if available. + Defaults to None. + + Returns: + None if request handling is complete. + A new AppServerRequest instance if internal redirect is required. + """ + raise NotImplementedError + + def EndRedirect(self, dispatched_output, original_output): + """Process the end of an internal redirect. + + This method is called after all subsequent dispatch requests have finished. + By default the output from the dispatched process is copied to the original. + + This will not be called on dispatchers that do not return an internal + redirect. + + Args: + dispatched_output: StringIO buffer containing the results from the + dispatched + original_output: The original output file. + """ + original_output.write(dispatched_output.read()) + + +class URLMatcher(object): + """Matches an arbitrary URL using a list of URL patterns from an application. + + Each URL pattern has an associated URLDispatcher instance and path to the + resource's location on disk. See AddURL for more details. The first pattern + that matches an inputted URL will have its associated values returned by + Match(). + """ + + def __init__(self): + """Initializer.""" + self._url_patterns = [] + + def AddURL(self, regex, dispatcher, path, requires_login, admin_only, + auth_fail_action): + """Adds a URL pattern to the list of patterns. + + If the supplied regex starts with a '^' or ends with a '$' an + InvalidAppConfigError exception will be raised. Start and end symbols + and implicitly added to all regexes, meaning we assume that all regexes + consume all input from a URL. + + Args: + regex: String containing the regular expression pattern. + dispatcher: Instance of URLDispatcher that should handle requests that + match this regex. + path: Path on disk for the resource. May contain back-references like + r'\1', r'\2', etc, which will be replaced by the corresponding groups + matched by the regex if present. + requires_login: True if the user must be logged-in before accessing this + URL; False if anyone can access this URL. + admin_only: True if the user must be a logged-in administrator to + access the URL; False if anyone can access the URL. + auth_fail_action: either appinfo.AUTH_FAIL_ACTION_REDIRECT (default) + which indicates that the server should redirect to the login page when + an authentication is needed, or appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED + which indicates that the server should just return a 401 Unauthorized + message immediately. + + Raises: + TypeError: if dispatcher is not a URLDispatcher sub-class instance. + InvalidAppConfigError: if regex isn't valid. + """ + if not isinstance(dispatcher, URLDispatcher): + raise TypeError('dispatcher must be a URLDispatcher sub-class') + + if regex.startswith('^') or regex.endswith('$'): + raise InvalidAppConfigError('regex starts with "^" or ends with "$"') + + adjusted_regex = '^%s$' % regex + + try: + url_re = re.compile(adjusted_regex) + except re.error, e: + raise InvalidAppConfigError('regex invalid: %s' % e) + + match_tuple = (url_re, dispatcher, path, requires_login, admin_only, + auth_fail_action) + self._url_patterns.append(match_tuple) + + def Match(self, + relative_url, + split_url=SplitURL): + """Matches a URL from a request against the list of URL patterns. + + The supplied relative_url may include the query string (i.e., the '?' + character and everything following). + + Args: + relative_url: Relative URL being accessed in a request. + split_url: Used for dependency injection. + + Returns: + Tuple (dispatcher, matched_path, requires_login, admin_only, + auth_fail_action), which are the corresponding values passed to + AddURL when the matching URL pattern was added to this matcher. + The matched_path will have back-references replaced using values + matched by the URL pattern. If no match was found, dispatcher will + be None. + """ + adjusted_url, unused_query_string = split_url(relative_url) + + for url_tuple in self._url_patterns: + url_re, dispatcher, path, requires_login, admin_only, auth_fail_action = url_tuple + the_match = url_re.match(adjusted_url) + + if the_match: + adjusted_path = the_match.expand(path) + return (dispatcher, adjusted_path, requires_login, admin_only, + auth_fail_action) + + return None, None, None, None, None + + def GetDispatchers(self): + """Retrieves the URLDispatcher objects that could be matched. + + Should only be used in tests. + + Returns: + A set of URLDispatcher objects. + """ + return set([url_tuple[1] for url_tuple in self._url_patterns]) + + + +class MatcherDispatcher(URLDispatcher): + """Dispatcher across multiple URLMatcher instances.""" + + def __init__(self, + login_url, + url_matchers, + get_user_info=dev_appserver_login.GetUserInfo, + login_redirect=dev_appserver_login.LoginRedirect): + """Initializer. + + Args: + login_url: Relative URL which should be used for handling user logins. + url_matchers: Sequence of URLMatcher objects. + get_user_info: Used for dependency injection. + login_redirect: Used for dependency injection. + """ + self._login_url = login_url + self._url_matchers = tuple(url_matchers) + self._get_user_info = get_user_info + self._login_redirect = login_redirect + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Dispatches a request to the first matching dispatcher. + + Matchers are checked in the order they were supplied to the constructor. + If no matcher matches, a 404 error will be written to the outfile. The + path variable supplied to this method is ignored. + + The value of request.path is ignored. + """ + cookies = ', '.join(request.headers.getheaders('cookie')) + email_addr, admin, user_id = self._get_user_info(cookies) + + for matcher in self._url_matchers: + dispatcher, matched_path, requires_login, admin_only, auth_fail_action = matcher.Match(request.relative_url) + if dispatcher is None: + continue + + logging.debug('Matched "%s" to %s with path %s', + request.relative_url, dispatcher, matched_path) + + if ((requires_login or admin_only) and + not email_addr and + not request.force_admin): + logging.debug('Login required, redirecting user') + if auth_fail_action == appinfo.AUTH_FAIL_ACTION_REDIRECT: + self._login_redirect(self._login_url, + base_env_dict['SERVER_NAME'], + base_env_dict['SERVER_PORT'], + request.relative_url, + outfile) + elif auth_fail_action == appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED: + outfile.write('Status: %d Not authorized\r\n' + '\r\n' + 'Login required to view page.' + % (httplib.UNAUTHORIZED)) + elif admin_only and not admin and not request.force_admin: + outfile.write('Status: %d Not authorized\r\n' + '\r\n' + 'Current logged in user %s is not ' + 'authorized to view this page.' + % (httplib.FORBIDDEN, email_addr)) + else: + request.path = matched_path + forward_request = dispatcher.Dispatch(request, + outfile, + base_env_dict=base_env_dict) + + if forward_request: + logging.info('Internal redirection to %s', + forward_request.relative_url) + new_outfile = cStringIO.StringIO() + self.Dispatch(forward_request, + new_outfile, + dict(base_env_dict)) + new_outfile.seek(0) + dispatcher.EndRedirect(new_outfile, outfile) + + return + + outfile.write('Status: %d URL did not match\r\n' + '\r\n' + 'Not found error: %s did not match any patterns ' + 'in application configuration.' + % (httplib.NOT_FOUND, request.relative_url)) + + + +class ApplicationLoggingHandler(logging.Handler): + """Python Logging handler that displays the debugging console to users.""" + + _COOKIE_NAME = '_ah_severity' + + _TEMPLATES_INITIALIZED = False + _HEADER = None + _SCRIPT = None + _MIDDLE = None + _FOOTER = None + + @staticmethod + def InitializeTemplates(header, script, middle, footer): + """Initializes the templates used to render the debugging console. + + This method must be called before any ApplicationLoggingHandler instances + are created. + + Args: + header: The header template that is printed first. + script: The script template that is printed after the logging messages. + middle: The middle element that's printed before the footer. + footer; The last element that's printed at the end of the document. + """ + ApplicationLoggingHandler._HEADER = header + ApplicationLoggingHandler._SCRIPT = script + ApplicationLoggingHandler._MIDDLE = middle + ApplicationLoggingHandler._FOOTER = footer + ApplicationLoggingHandler._TEMPLATES_INITIALIZED = True + + @staticmethod + def AreTemplatesInitialized(): + """Returns True if InitializeTemplates has been called, False otherwise.""" + return ApplicationLoggingHandler._TEMPLATES_INITIALIZED + + def __init__(self, *args, **kwargs): + """Initializer. + + Args: + args, kwargs: See logging.Handler. + + Raises: + TemplatesNotLoadedError exception if the InitializeTemplates method was + not called before creating this instance. + """ + if not self._TEMPLATES_INITIALIZED: + raise TemplatesNotLoadedError + + logging.Handler.__init__(self, *args, **kwargs) + self._record_list = [] + self._start_time = time.time() + + def emit(self, record): + """Called by the logging module each time the application logs a message. + + Args: + record: logging.LogRecord instance corresponding to the newly logged + message. + """ + self._record_list.append(record) + + def AddDebuggingConsole(self, relative_url, env, outfile): + """Prints an HTML debugging console to an output stream, if requested. + + Args: + relative_url: Relative URL that was accessed, including the query string. + Used to determine if the parameter 'debug' was supplied, in which case + the console will be shown. + env: Dictionary containing CGI environment variables. Checks for the + HTTP_COOKIE entry to see if the accessing user has any logging-related + cookies set. + outfile: Output stream to which the console should be written if either + a debug parameter was supplied or a logging cookie is present. + """ + unused_script_name, query_string = SplitURL(relative_url) + param_dict = cgi.parse_qs(query_string, True) + cookie_dict = Cookie.SimpleCookie(env.get('HTTP_COOKIE', '')) + if 'debug' not in param_dict and self._COOKIE_NAME not in cookie_dict: + return + + outfile.write(self._HEADER) + for record in self._record_list: + self._PrintRecord(record, outfile) + + outfile.write(self._MIDDLE) + outfile.write(self._SCRIPT) + outfile.write(self._FOOTER) + + def _PrintRecord(self, record, outfile): + """Prints a single logging record to an output stream. + + Args: + record: logging.LogRecord instance to print. + outfile: Output stream to which the LogRecord should be printed. + """ + message = cgi.escape(record.getMessage()) + level_name = logging.getLevelName(record.levelno).lower() + level_letter = level_name[:1].upper() + time_diff = record.created - self._start_time + outfile.write('\n' % level_name) + outfile.write('%2.5f %s >\n' + % (level_name, time_diff, level_letter)) + outfile.write('%s\n' % message) + outfile.write('\n') + + +_IGNORE_REQUEST_HEADERS = frozenset(['content-type', 'content-length', + 'accept-encoding', 'transfer-encoding']) + + +def SetupEnvironment(cgi_path, + relative_url, + headers, + infile, + split_url=SplitURL, + get_user_info=dev_appserver_login.GetUserInfo): + """Sets up environment variables for a CGI. + + Args: + cgi_path: Full file-system path to the CGI being executed. + relative_url: Relative URL used to access the CGI. + headers: Instance of mimetools.Message containing request headers. + infile: File-like object with input data from the request. + split_url, get_user_info: Used for dependency injection. + + Returns: + Dictionary containing CGI environment variables. + """ + env = DEFAULT_ENV.copy() + + script_name, query_string = split_url(relative_url) + + env['SCRIPT_NAME'] = '' + env['QUERY_STRING'] = query_string + env['PATH_INFO'] = urllib.unquote(script_name) + env['PATH_TRANSLATED'] = cgi_path + env['CONTENT_TYPE'] = headers.getheader('content-type', + 'application/x-www-form-urlencoded') + env['CONTENT_LENGTH'] = headers.getheader('content-length', '') + + cookies = ', '.join(headers.getheaders('cookie')) + email_addr, admin, user_id = get_user_info(cookies) + env['USER_EMAIL'] = email_addr + env['USER_ID'] = user_id + if admin: + env['USER_IS_ADMIN'] = '1' + if env['AUTH_DOMAIN'] == '*': + auth_domain = 'gmail.com' + parts = email_addr.split('@') + if len(parts) == 2 and parts[1]: + auth_domain = parts[1] + env['AUTH_DOMAIN'] = auth_domain + + for key in headers: + if key in _IGNORE_REQUEST_HEADERS: + continue + adjusted_name = key.replace('-', '_').upper() + env['HTTP_' + adjusted_name] = ', '.join(headers.getheaders(key)) + + if DEVEL_PAYLOAD_HEADER in env: + del env[DEVEL_PAYLOAD_HEADER] + new_data = base64.standard_b64decode(infile.getvalue()) + infile.seek(0) + infile.truncate() + infile.write(new_data) + infile.seek(0) + env['CONTENT_LENGTH'] = str(len(new_data)) + + return env + + +def NotImplementedFake(*args, **kwargs): + """Fake for methods/functions that are not implemented in the production + environment. + """ + raise NotImplementedError('This class/method is not available.') + + +class NotImplementedFakeClass(object): + """Fake class for classes that are not implemented in the production env. + """ + __init__ = NotImplementedFake + + +def IsEncodingsModule(module_name): + """Determines if the supplied module is related to encodings in any way. + + Encodings-related modules cannot be reloaded, so they need to be treated + specially when sys.modules is modified in any way. + + Args: + module_name: Absolute name of the module regardless of how it is imported + into the local namespace (e.g., foo.bar.baz). + + Returns: + True if it's an encodings-related module; False otherwise. + """ + if (module_name in ('codecs', 'encodings') or + module_name.startswith('encodings.')): + return True + return False + + +def ClearAllButEncodingsModules(module_dict): + """Clear all modules in a module dictionary except for those modules that + are in any way related to encodings. + + Args: + module_dict: Dictionary in the form used by sys.modules. + """ + for module_name in module_dict.keys(): + if not IsEncodingsModule(module_name): + del module_dict[module_name] + + +def FakeURandom(n): + """Fake version of os.urandom.""" + bytes = '' + for _ in range(n): + bytes += chr(random.randint(0, 255)) + return bytes + + +def FakeUname(): + """Fake version of os.uname.""" + return ('Linux', '', '', '', '') + + +def FakeUnlink(path): + """Fake version of os.unlink.""" + if os.path.isdir(path): + raise OSError(errno.ENOENT, "Is a directory", path) + else: + raise OSError(errno.EPERM, "Operation not permitted", path) + + +def FakeReadlink(path): + """Fake version of os.readlink.""" + raise OSError(errno.EINVAL, "Invalid argument", path) + + +def FakeAccess(path, mode): + """Fake version of os.access where only reads are supported.""" + if not os.path.exists(path) or mode != os.R_OK: + return False + else: + return True + + +def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale): + """Fake version of locale.setlocale that only supports the default.""" + if value not in (None, '', 'C', 'POSIX'): + raise locale.Error('locale emulation only supports "C" locale') + return original_setlocale(category, 'C') + + +def FakeOpen(filename, flags, mode=0777): + """Fake version of os.open.""" + raise OSError(errno.EPERM, "Operation not permitted", filename) + + +def FakeRename(src, dst): + """Fake version of os.rename.""" + raise OSError(errno.EPERM, "Operation not permitted", src) + + +def FakeUTime(path, times): + """Fake version of os.utime.""" + raise OSError(errno.EPERM, "Operation not permitted", path) + + +def FakeGetPlatform(): + """Fake distutils.util.get_platform on OS/X. Pass-through otherwise.""" + if sys.platform == 'darwin': + return 'macosx-' + else: + return distutils.util.get_platform() + + +def IsPathInSubdirectories(filename, + subdirectories, + normcase=os.path.normcase): + """Determines if a filename is contained within one of a set of directories. + + Args: + filename: Path of the file (relative or absolute). + subdirectories: Iterable collection of paths to subdirectories which the + given filename may be under. + normcase: Used for dependency injection. + + Returns: + True if the supplied filename is in one of the given sub-directories or + its hierarchy of children. False otherwise. + """ + file_dir = normcase(os.path.dirname(os.path.abspath(filename))) + for parent in subdirectories: + fixed_parent = normcase(os.path.abspath(parent)) + if os.path.commonprefix([file_dir, fixed_parent]) == fixed_parent: + return True + return False + +SHARED_MODULE_PREFIXES = set([ + 'google', + 'logging', + 'sys', + 'warnings', + + + + + 're', + 'sre_compile', + 'sre_constants', + 'sre_parse', + + + 'email', + + + + + 'wsgiref', +]) + +NOT_SHARED_MODULE_PREFIXES = set([ + 'google.appengine.ext', +]) + + +def ModuleNameHasPrefix(module_name, prefix_set): + """Determines if a module's name belongs to a set of prefix strings. + + Args: + module_name: String containing the fully qualified module name. + prefix_set: Iterable set of module name prefixes to check against. + + Returns: + True if the module_name belongs to the prefix set or is a submodule of + any of the modules specified in the prefix_set. Otherwise False. + """ + for prefix in prefix_set: + if prefix == module_name: + return True + + if module_name.startswith(prefix + '.'): + return True + + return False + + +def SetupSharedModules(module_dict): + """Creates a module dictionary for the hardened part of the process. + + Module dictionary will contain modules that should be shared between the + hardened and unhardened parts of the process. + + Args: + module_dict: Module dictionary from which existing modules should be + pulled (usually sys.modules). + + Returns: + A new module dictionary. + """ + output_dict = {} + for module_name, module in module_dict.iteritems(): + if module is None: + continue + + if IsEncodingsModule(module_name): + output_dict[module_name] = module + continue + + shared_prefix = ModuleNameHasPrefix(module_name, SHARED_MODULE_PREFIXES) + banned_prefix = ModuleNameHasPrefix(module_name, NOT_SHARED_MODULE_PREFIXES) + + if shared_prefix and not banned_prefix: + output_dict[module_name] = module + + return output_dict + + +def GeneratePythonPaths(*p): + """Generate all valid filenames for the given file. + + Args: + p: Positional args are the folders to the file and finally the file + without a suffix. + + Returns: + A list of strings representing the given path to a file with each valid + suffix for this python build. + """ + suffixes = imp.get_suffixes() + return [os.path.join(*p) + s for s, m, t in suffixes] + + +class FakeFile(file): + """File sub-class that enforces the security restrictions of the production + environment. + """ + + ALLOWED_MODES = frozenset(['r', 'rb', 'U', 'rU']) + + ALLOWED_FILES = set(os.path.normcase(filename) + for filename in mimetypes.knownfiles + if os.path.isfile(filename)) + + ALLOWED_DIRS = set([ + os.path.normcase(os.path.realpath(os.path.dirname(os.__file__))), + os.path.normcase(os.path.abspath(os.path.dirname(os.__file__))), + os.path.normcase(os.path.dirname(os.path.realpath(os.__file__))), + os.path.normcase(os.path.dirname(os.path.abspath(os.__file__))), + ]) + + NOT_ALLOWED_DIRS = set([ + + + + + SITE_PACKAGES, + ]) + + ALLOWED_SITE_PACKAGE_DIRS = set( + os.path.normcase(os.path.abspath(os.path.join(SITE_PACKAGES, path))) + for path in [ + + ]) + + ALLOWED_SITE_PACKAGE_FILES = set( + os.path.normcase(os.path.abspath(os.path.join( + os.path.dirname(os.__file__), 'site-packages', path))) + for path in itertools.chain(*[ + + [os.path.join('Crypto')], + GeneratePythonPaths('Crypto', '__init__'), + [os.path.join('Crypto', 'Cipher')], + GeneratePythonPaths('Crypto', 'Cipher', '__init__'), + GeneratePythonPaths('Crypto', 'Cipher', 'AES'), + GeneratePythonPaths('Crypto', 'Cipher', 'ARC2'), + GeneratePythonPaths('Crypto', 'Cipher', 'ARC4'), + GeneratePythonPaths('Crypto', 'Cipher', 'Blowfish'), + GeneratePythonPaths('Crypto', 'Cipher', 'CAST'), + GeneratePythonPaths('Crypto', 'Cipher', 'DES'), + GeneratePythonPaths('Crypto', 'Cipher', 'DES3'), + GeneratePythonPaths('Crypto', 'Cipher', 'XOR'), + [os.path.join('Crypto', 'Hash')], + GeneratePythonPaths('Crypto', 'Hash', '__init__'), + GeneratePythonPaths('Crypto', 'Hash', 'HMAC'), + os.path.join('Crypto', 'Hash', 'MD2'), + os.path.join('Crypto', 'Hash', 'MD4'), + GeneratePythonPaths('Crypto', 'Hash', 'MD5'), + GeneratePythonPaths('Crypto', 'Hash', 'SHA'), + os.path.join('Crypto', 'Hash', 'SHA256'), + os.path.join('Crypto', 'Hash', 'RIPEMD'), + [os.path.join('Crypto', 'Protocol')], + GeneratePythonPaths('Crypto', 'Protocol', '__init__'), + GeneratePythonPaths('Crypto', 'Protocol', 'AllOrNothing'), + GeneratePythonPaths('Crypto', 'Protocol', 'Chaffing'), + [os.path.join('Crypto', 'PublicKey')], + GeneratePythonPaths('Crypto', 'PublicKey', '__init__'), + GeneratePythonPaths('Crypto', 'PublicKey', 'DSA'), + GeneratePythonPaths('Crypto', 'PublicKey', 'ElGamal'), + GeneratePythonPaths('Crypto', 'PublicKey', 'RSA'), + GeneratePythonPaths('Crypto', 'PublicKey', 'pubkey'), + GeneratePythonPaths('Crypto', 'PublicKey', 'qNEW'), + [os.path.join('Crypto', 'Util')], + GeneratePythonPaths('Crypto', 'Util', '__init__'), + GeneratePythonPaths('Crypto', 'Util', 'RFC1751'), + GeneratePythonPaths('Crypto', 'Util', 'number'), + GeneratePythonPaths('Crypto', 'Util', 'randpool'), + ])) + + _original_file = file + + _root_path = None + _application_paths = None + _skip_files = None + _static_file_config_matcher = None + + _allow_skipped_files = True + + _availability_cache = {} + + @staticmethod + def SetAllowedPaths(root_path, application_paths): + """Configures which paths are allowed to be accessed. + + Must be called at least once before any file objects are created in the + hardened environment. + + Args: + root_path: Absolute path to the root of the application. + application_paths: List of additional paths that the application may + access, this must include the App Engine runtime but + not the Python library directories. + """ + FakeFile._application_paths = (set(os.path.realpath(path) + for path in application_paths) | + set(os.path.abspath(path) + for path in application_paths)) + FakeFile._application_paths.add(root_path) + + FakeFile._root_path = os.path.join(root_path, '') + + FakeFile._availability_cache = {} + + @staticmethod + def SetAllowSkippedFiles(allow_skipped_files): + """Configures access to files matching FakeFile._skip_files. + + Args: + allow_skipped_files: Boolean whether to allow access to skipped files + """ + FakeFile._allow_skipped_files = allow_skipped_files + FakeFile._availability_cache = {} + + @staticmethod + def SetAllowedModule(name): + """Allow the use of a module based on where it is located. + + Meant to be used by use_library() so that it has a link back into the + trusted part of the interpreter. + + Args: + name: Name of the module to allow. + """ + stream, pathname, description = imp.find_module(name) + pathname = os.path.normcase(os.path.abspath(pathname)) + if stream: + stream.close() + FakeFile.ALLOWED_FILES.add(pathname) + FakeFile.ALLOWED_FILES.add(os.path.realpath(pathname)) + else: + assert description[2] == imp.PKG_DIRECTORY + if pathname.startswith(SITE_PACKAGES): + FakeFile.ALLOWED_SITE_PACKAGE_DIRS.add(pathname) + FakeFile.ALLOWED_SITE_PACKAGE_DIRS.add(os.path.realpath(pathname)) + else: + FakeFile.ALLOWED_DIRS.add(pathname) + FakeFile.ALLOWED_DIRS.add(os.path.realpath(pathname)) + + @staticmethod + def SetSkippedFiles(skip_files): + """Sets which files in the application directory are to be ignored. + + Must be called at least once before any file objects are created in the + hardened environment. + + Must be called whenever the configuration was updated. + + Args: + skip_files: Object with .match() method (e.g. compiled regexp). + """ + FakeFile._skip_files = skip_files + FakeFile._availability_cache = {} + + @staticmethod + def SetStaticFileConfigMatcher(static_file_config_matcher): + """Sets StaticFileConfigMatcher instance for checking if a file is static. + + Must be called at least once before any file objects are created in the + hardened environment. + + Must be called whenever the configuration was updated. + + Args: + static_file_config_matcher: StaticFileConfigMatcher instance. + """ + FakeFile._static_file_config_matcher = static_file_config_matcher + FakeFile._availability_cache = {} + + @staticmethod + def IsFileAccessible(filename, normcase=os.path.normcase): + """Determines if a file's path is accessible. + + SetAllowedPaths(), SetSkippedFiles() and SetStaticFileConfigMatcher() must + be called before this method or else all file accesses will raise an error. + + Args: + filename: Path of the file to check (relative or absolute). May be a + directory, in which case access for files inside that directory will + be checked. + normcase: Used for dependency injection. + + Returns: + True if the file is accessible, False otherwise. + """ + logical_filename = normcase(os.path.abspath(filename)) + + result = FakeFile._availability_cache.get(logical_filename) + if result is None: + result = FakeFile._IsFileAccessibleNoCache(logical_filename, + normcase=normcase) + FakeFile._availability_cache[logical_filename] = result + return result + + @staticmethod + def _IsFileAccessibleNoCache(logical_filename, normcase=os.path.normcase): + """Determines if a file's path is accessible. + + This is an internal part of the IsFileAccessible implementation. + + Args: + logical_filename: Absolute path of the file to check. + normcase: Used for dependency injection. + + Returns: + True if the file is accessible, False otherwise. + """ + logical_dirfakefile = logical_filename + if os.path.isdir(logical_filename): + logical_dirfakefile = os.path.join(logical_filename, 'foo') + + if IsPathInSubdirectories(logical_dirfakefile, [FakeFile._root_path], + normcase=normcase): + relative_filename = logical_dirfakefile[len(FakeFile._root_path):] + + if not FakeFile._allow_skipped_files: + path = relative_filename + while path != os.path.dirname(path): + if FakeFile._skip_files.match(path): + logging.warning('Blocking access to skipped file "%s"', + logical_filename) + return False + path = os.path.dirname(path) + + if FakeFile._static_file_config_matcher.IsStaticFile(relative_filename): + logging.warning('Blocking access to static file "%s"', + logical_filename) + return False + + if logical_filename in FakeFile.ALLOWED_FILES: + return True + + if logical_filename in FakeFile.ALLOWED_SITE_PACKAGE_FILES: + return True + + if IsPathInSubdirectories(logical_dirfakefile, + FakeFile.ALLOWED_SITE_PACKAGE_DIRS, + normcase=normcase): + return True + + allowed_dirs = FakeFile._application_paths | FakeFile.ALLOWED_DIRS + if (IsPathInSubdirectories(logical_dirfakefile, + allowed_dirs, + normcase=normcase) and + not IsPathInSubdirectories(logical_dirfakefile, + FakeFile.NOT_ALLOWED_DIRS, + normcase=normcase)): + return True + + return False + + def __init__(self, filename, mode='r', bufsize=-1, **kwargs): + """Initializer. See file built-in documentation.""" + if mode not in FakeFile.ALLOWED_MODES: + raise IOError('invalid mode: %s' % mode) + + if not FakeFile.IsFileAccessible(filename): + raise IOError(errno.EACCES, 'file not accessible', filename) + + super(FakeFile, self).__init__(filename, mode, bufsize, **kwargs) + + +from google.appengine.dist import _library +_library.SetAllowedModule = FakeFile.SetAllowedModule + + +class RestrictedPathFunction(object): + """Enforces access restrictions for functions that have a file or + directory path as their first argument.""" + + _original_os = os + + def __init__(self, original_func): + """Initializer. + + Args: + original_func: Callable that takes as its first argument the path to a + file or directory on disk; all subsequent arguments may be variable. + """ + self._original_func = original_func + + def __call__(self, path, *args, **kwargs): + """Enforces access permissions for the function passed to the constructor. + """ + if not FakeFile.IsFileAccessible(path): + raise OSError(errno.EACCES, 'path not accessible', path) + + return self._original_func(path, *args, **kwargs) + + +def GetSubmoduleName(fullname): + """Determines the leaf submodule name of a full module name. + + Args: + fullname: Fully qualified module name, e.g. 'foo.bar.baz' + + Returns: + Submodule name, e.g. 'baz'. If the supplied module has no submodule (e.g., + 'stuff'), the returned value will just be that module name ('stuff'). + """ + return fullname.rsplit('.', 1)[-1] + + + +class CouldNotFindModuleError(ImportError): + """Raised when a module could not be found. + + In contrast to when a module has been found, but cannot be loaded because of + hardening restrictions. + """ + + +def Trace(func): + """Call stack logging decorator for HardenedModulesHook class. + + This decorator logs the call stack of the HardenedModulesHook class as + it executes, indenting logging messages based on the current stack depth. + + Args: + func: the function to decorate. + + Returns: + The decorated function. + """ + + def Decorate(self, *args, **kwargs): + args_to_show = [] + if args is not None: + args_to_show.extend(str(argument) for argument in args) + if kwargs is not None: + args_to_show.extend('%s=%s' % (key, value) + for key, value in kwargs.iteritems()) + + args_string = ', '.join(args_to_show) + + self.log('Entering %s(%s)', func.func_name, args_string) + self._indent_level += 1 + try: + return func(self, *args, **kwargs) + finally: + self._indent_level -= 1 + self.log('Exiting %s(%s)', func.func_name, args_string) + + return Decorate + + +class HardenedModulesHook(object): + """Meta import hook that restricts the modules used by applications to match + the production environment. + + Module controls supported: + - Disallow native/extension modules from being loaded + - Disallow built-in and/or Python-distributed modules from being loaded + - Replace modules with completely empty modules + - Override specific module attributes + - Replace one module with another + + After creation, this object should be added to the front of the sys.meta_path + list (which may need to be created). The sys.path_importer_cache dictionary + should also be cleared, to prevent loading any non-restricted modules. + + See PEP302 for more info on how this works: + http://www.python.org/dev/peps/pep-0302/ + """ + + ENABLE_LOGGING = False + + def log(self, message, *args): + """Logs an import-related message to stderr, with indentation based on + current call-stack depth. + + Args: + message: Logging format string. + args: Positional format parameters for the logging message. + """ + if HardenedModulesHook.ENABLE_LOGGING: + indent = self._indent_level * ' ' + print >>sys.stderr, indent + (message % args) + + _WHITE_LIST_C_MODULES = [ + 'py_streamhtmlparser', + 'AES', + 'ARC2', + 'ARC4', + 'Blowfish', + 'CAST', + 'DES', + 'DES3', + 'MD2', + 'MD4', + 'RIPEMD', + 'SHA256', + 'XOR', + + '_Crypto_Cipher__AES', + '_Crypto_Cipher__ARC2', + '_Crypto_Cipher__ARC4', + '_Crypto_Cipher__Blowfish', + '_Crypto_Cipher__CAST', + '_Crypto_Cipher__DES', + '_Crypto_Cipher__DES3', + '_Crypto_Cipher__XOR', + '_Crypto_Hash__MD2', + '_Crypto_Hash__MD4', + '_Crypto_Hash__RIPEMD', + '_Crypto_Hash__SHA256', + 'array', + 'binascii', + 'bz2', + 'cmath', + 'collections', + 'crypt', + 'cStringIO', + 'datetime', + 'errno', + 'exceptions', + 'gc', + 'itertools', + 'math', + 'md5', + 'operator', + 'posix', + 'posixpath', + 'pyexpat', + 'sha', + 'struct', + 'sys', + 'time', + 'timing', + 'unicodedata', + 'zlib', + '_ast', + '_bisect', + '_codecs', + '_codecs_cn', + '_codecs_hk', + '_codecs_iso2022', + '_codecs_jp', + '_codecs_kr', + '_codecs_tw', + '_collections', + '_csv', + '_elementtree', + '_functools', + '_hashlib', + '_heapq', + '_locale', + '_lsprof', + '_md5', + '_multibytecodec', + '_scproxy', + '_random', + '_sha', + '_sha256', + '_sha512', + '_sre', + '_struct', + '_types', + '_weakref', + '__main__', + ] + + __CRYPTO_CIPHER_ALLOWED_MODULES = [ + 'MODE_CBC', + 'MODE_CFB', + 'MODE_CTR', + 'MODE_ECB', + 'MODE_OFB', + 'block_size', + 'key_size', + 'new', + ] + _WHITE_LIST_PARTIAL_MODULES = { + 'Crypto.Cipher.AES': __CRYPTO_CIPHER_ALLOWED_MODULES, + 'Crypto.Cipher.ARC2': __CRYPTO_CIPHER_ALLOWED_MODULES, + 'Crypto.Cipher.Blowfish': __CRYPTO_CIPHER_ALLOWED_MODULES, + 'Crypto.Cipher.CAST': __CRYPTO_CIPHER_ALLOWED_MODULES, + 'Crypto.Cipher.DES': __CRYPTO_CIPHER_ALLOWED_MODULES, + 'Crypto.Cipher.DES3': __CRYPTO_CIPHER_ALLOWED_MODULES, + + 'gc': [ + 'enable', + 'disable', + 'isenabled', + 'collect', + 'get_debug', + 'set_threshold', + 'get_threshold', + 'get_count' + ], + + + + 'os': [ + 'access', + 'altsep', + 'curdir', + 'defpath', + 'devnull', + 'environ', + 'error', + 'extsep', + 'EX_NOHOST', + 'EX_NOINPUT', + 'EX_NOPERM', + 'EX_NOUSER', + 'EX_OK', + 'EX_OSERR', + 'EX_OSFILE', + 'EX_PROTOCOL', + 'EX_SOFTWARE', + 'EX_TEMPFAIL', + 'EX_UNAVAILABLE', + 'EX_USAGE', + 'F_OK', + 'getcwd', + 'getcwdu', + 'getenv', + 'listdir', + 'lstat', + 'name', + 'NGROUPS_MAX', + 'O_APPEND', + 'O_CREAT', + 'O_DIRECT', + 'O_DIRECTORY', + 'O_DSYNC', + 'O_EXCL', + 'O_LARGEFILE', + 'O_NDELAY', + 'O_NOCTTY', + 'O_NOFOLLOW', + 'O_NONBLOCK', + 'O_RDONLY', + 'O_RDWR', + 'O_RSYNC', + 'O_SYNC', + 'O_TRUNC', + 'O_WRONLY', + 'open', + 'pardir', + 'path', + 'pathsep', + 'R_OK', + 'readlink', + 'remove', + 'rename', + 'SEEK_CUR', + 'SEEK_END', + 'SEEK_SET', + 'sep', + 'stat', + 'stat_float_times', + 'stat_result', + 'strerror', + 'TMP_MAX', + 'unlink', + 'urandom', + 'utime', + 'walk', + 'WCOREDUMP', + 'WEXITSTATUS', + 'WIFEXITED', + 'WIFSIGNALED', + 'WIFSTOPPED', + 'WNOHANG', + 'WSTOPSIG', + 'WTERMSIG', + 'WUNTRACED', + 'W_OK', + 'X_OK', + ], + } + + _MODULE_OVERRIDES = { + 'locale': { + 'setlocale': FakeSetLocale, + }, + + 'os': { + 'access': FakeAccess, + 'listdir': RestrictedPathFunction(os.listdir), + + 'lstat': RestrictedPathFunction(os.stat), + 'open': FakeOpen, + 'readlink': FakeReadlink, + 'remove': FakeUnlink, + 'rename': FakeRename, + 'stat': RestrictedPathFunction(os.stat), + 'uname': FakeUname, + 'unlink': FakeUnlink, + 'urandom': FakeURandom, + 'utime': FakeUTime, + }, + + 'distutils.util': { + 'get_platform': FakeGetPlatform, + }, + } + + _ENABLED_FILE_TYPES = ( + imp.PKG_DIRECTORY, + imp.PY_SOURCE, + imp.PY_COMPILED, + imp.C_BUILTIN, + ) + + def __init__(self, + module_dict, + imp_module=imp, + os_module=os, + dummy_thread_module=dummy_thread, + pickle_module=pickle): + """Initializer. + + Args: + module_dict: Module dictionary to use for managing system modules. + Should be sys.modules. + imp_module, os_module, dummy_thread_module, pickle_module: References to + modules that exist in the dev_appserver that must be used by this class + in order to function, even if these modules have been unloaded from + sys.modules. + """ + self._module_dict = module_dict + self._imp = imp_module + self._os = os_module + self._dummy_thread = dummy_thread_module + self._pickle = pickle + self._indent_level = 0 + + @Trace + def find_module(self, fullname, path=None): + """See PEP 302.""" + if fullname in ('cPickle', 'thread'): + return self + + search_path = path + all_modules = fullname.split('.') + try: + for index, current_module in enumerate(all_modules): + current_module_fullname = '.'.join(all_modules[:index + 1]) + if (current_module_fullname == fullname and not + self.StubModuleExists(fullname)): + self.FindModuleRestricted(current_module, + current_module_fullname, + search_path) + else: + if current_module_fullname in self._module_dict: + module = self._module_dict[current_module_fullname] + else: + module = self.FindAndLoadModule(current_module, + current_module_fullname, + search_path) + + if hasattr(module, '__path__'): + search_path = module.__path__ + except CouldNotFindModuleError: + return None + + return self + + def StubModuleExists(self, name): + """Check if the named module has a stub replacement.""" + if name in sys.builtin_module_names: + name = 'py_%s' % name + if name in dist.__all__: + return True + return False + + def ImportStubModule(self, name): + """Import the stub module replacement for the specified module.""" + if name in sys.builtin_module_names: + name = 'py_%s' % name + module = __import__(dist.__name__, {}, {}, [name]) + return getattr(module, name) + + @Trace + def FixModule(self, module): + """Prunes and overrides restricted module attributes. + + Args: + module: The module to prune. This should be a new module whose attributes + reference back to the real module's __dict__ members. + """ + if module.__name__ in self._WHITE_LIST_PARTIAL_MODULES: + allowed_symbols = self._WHITE_LIST_PARTIAL_MODULES[module.__name__] + for symbol in set(module.__dict__) - set(allowed_symbols): + if not (symbol.startswith('__') and symbol.endswith('__')): + del module.__dict__[symbol] + + if module.__name__ in self._MODULE_OVERRIDES: + module.__dict__.update(self._MODULE_OVERRIDES[module.__name__]) + + @Trace + def FindModuleRestricted(self, + submodule, + submodule_fullname, + search_path): + """Locates a module while enforcing module import restrictions. + + Args: + submodule: The short name of the submodule (i.e., the last section of + the fullname; for 'foo.bar' this would be 'bar'). + submodule_fullname: The fully qualified name of the module to find (e.g., + 'foo.bar'). + search_path: List of paths to search for to find this module. Should be + None if the current sys.path should be used. + + Returns: + Tuple (source_file, pathname, description) where: + source_file: File-like object that contains the module; in the case + of packages, this will be None, which implies to look at __init__.py. + pathname: String containing the full path of the module on disk. + description: Tuple returned by imp.find_module(). + However, in the case of an import using a path hook (e.g. a zipfile), + source_file will be a PEP-302-style loader object, pathname will be None, + and description will be a tuple filled with None values. + + Raises: + ImportError exception if the requested module was found, but importing + it is disallowed. + + CouldNotFindModuleError exception if the request module could not even + be found for import. + """ + if search_path is None: + search_path = [None] + sys.path + for path_entry in search_path: + result = self.FindPathHook(submodule, submodule_fullname, path_entry) + if result is not None: + source_file, pathname, description = result + if description == (None, None, None): + return result + else: + break + else: + self.log('Could not find module "%s"', submodule_fullname) + raise CouldNotFindModuleError() + + suffix, mode, file_type = description + + if (file_type not in (self._imp.C_BUILTIN, self._imp.C_EXTENSION) and + not FakeFile.IsFileAccessible(pathname)): + error_message = 'Access to module file denied: %s' % pathname + logging.debug(error_message) + raise ImportError(error_message) + + if (file_type not in self._ENABLED_FILE_TYPES and + submodule not in self._WHITE_LIST_C_MODULES): + error_message = ('Could not import "%s": Disallowed C-extension ' + 'or built-in module' % submodule_fullname) + logging.debug(error_message) + raise ImportError(error_message) + + return source_file, pathname, description + + def FindPathHook(self, submodule, submodule_fullname, path_entry): + """Helper for FindModuleRestricted to find a module in a sys.path entry. + + Args: + submodule: + submodule_fullname: + path_entry: A single sys.path entry, or None representing the builtins. + + Returns: + Either None (if nothing was found), or a triple (source_file, path_name, + description). See the doc string for FindModuleRestricted() for the + meaning of the latter. + """ + if path_entry is None: + if submodule_fullname in sys.builtin_module_names: + try: + result = self._imp.find_module(submodule) + except ImportError: + pass + else: + source_file, pathname, description = result + suffix, mode, file_type = description + if file_type == self._imp.C_BUILTIN: + return result + return None + + + if path_entry in sys.path_importer_cache: + importer = sys.path_importer_cache[path_entry] + else: + importer = None + for hook in sys.path_hooks: + try: + importer = hook(path_entry) + break + except ImportError: + pass + sys.path_importer_cache[path_entry] = importer + + if importer is None: + try: + return self._imp.find_module(submodule, [path_entry]) + except ImportError: + pass + else: + loader = importer.find_module(submodule) + if loader is not None: + return (loader, None, (None, None, None)) + + return None + + @Trace + def LoadModuleRestricted(self, + submodule_fullname, + source_file, + pathname, + description): + """Loads a module while enforcing module import restrictions. + + As a byproduct, the new module will be added to the module dictionary. + + Args: + submodule_fullname: The fully qualified name of the module to find (e.g., + 'foo.bar'). + source_file: File-like object that contains the module's source code, + or a PEP-302-style loader object. + pathname: String containing the full path of the module on disk. + description: Tuple returned by imp.find_module(), or (None, None, None) + in case source_file is a PEP-302-style loader object. + + Returns: + The new module. + + Raises: + ImportError exception of the specified module could not be loaded for + whatever reason. + """ + if description == (None, None, None): + return source_file.load_module(submodule_fullname) + + try: + try: + return self._imp.load_module(submodule_fullname, + source_file, + pathname, + description) + except: + if submodule_fullname in self._module_dict: + del self._module_dict[submodule_fullname] + raise + + finally: + if source_file is not None: + source_file.close() + + @Trace + def FindAndLoadModule(self, + submodule, + submodule_fullname, + search_path): + """Finds and loads a module, loads it, and adds it to the module dictionary. + + Args: + submodule: Name of the module to import (e.g., baz). + submodule_fullname: Full name of the module to import (e.g., foo.bar.baz). + search_path: Path to use for searching for this submodule. For top-level + modules this should be None; otherwise it should be the __path__ + attribute from the parent package. + + Returns: + A new module instance that has been inserted into the module dictionary + supplied to __init__. + + Raises: + ImportError exception if the module could not be loaded for whatever + reason (e.g., missing, not allowed). + """ + module = self._imp.new_module(submodule_fullname) + + if submodule_fullname == 'thread': + module.__dict__.update(self._dummy_thread.__dict__) + module.__name__ = 'thread' + elif submodule_fullname == 'cPickle': + module.__dict__.update(self._pickle.__dict__) + module.__name__ = 'cPickle' + elif submodule_fullname == 'os': + module.__dict__.update(self._os.__dict__) + elif self.StubModuleExists(submodule_fullname): + module = self.ImportStubModule(submodule_fullname) + else: + source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path) + module = self.LoadModuleRestricted(submodule_fullname, + source_file, + pathname, + description) + + module.__loader__ = self + self.FixModule(module) + if submodule_fullname not in self._module_dict: + self._module_dict[submodule_fullname] = module + + if submodule_fullname == 'os': + os_path_name = module.path.__name__ + os_path = self.FindAndLoadModule(os_path_name, os_path_name, search_path) + self._module_dict['os.path'] = os_path + module.__dict__['path'] = os_path + + return module + + @Trace + def GetParentPackage(self, fullname): + """Retrieves the parent package of a fully qualified module name. + + Args: + fullname: Full name of the module whose parent should be retrieved (e.g., + foo.bar). + + Returns: + Module instance for the parent or None if there is no parent module. + + Raise: + ImportError exception if the module's parent could not be found. + """ + all_modules = fullname.split('.') + parent_module_fullname = '.'.join(all_modules[:-1]) + if parent_module_fullname: + if self.find_module(fullname) is None: + raise ImportError('Could not find module %s' % fullname) + + return self._module_dict[parent_module_fullname] + return None + + @Trace + def GetParentSearchPath(self, fullname): + """Determines the search path of a module's parent package. + + Args: + fullname: Full name of the module to look up (e.g., foo.bar). + + Returns: + Tuple (submodule, search_path) where: + submodule: The last portion of the module name from fullname (e.g., + if fullname is foo.bar, then this is bar). + search_path: List of paths that belong to the parent package's search + path or None if there is no parent package. + + Raises: + ImportError exception if the module or its parent could not be found. + """ + submodule = GetSubmoduleName(fullname) + parent_package = self.GetParentPackage(fullname) + search_path = None + if parent_package is not None and hasattr(parent_package, '__path__'): + search_path = parent_package.__path__ + return submodule, search_path + + @Trace + def GetModuleInfo(self, fullname): + """Determines the path on disk and the search path of a module or package. + + Args: + fullname: Full name of the module to look up (e.g., foo.bar). + + Returns: + Tuple (pathname, search_path, submodule) where: + pathname: String containing the full path of the module on disk, + or None if the module wasn't loaded from disk (e.g. from a zipfile). + search_path: List of paths that belong to the found package's search + path or None if found module is not a package. + submodule: The relative name of the submodule that's being imported. + """ + submodule, search_path = self.GetParentSearchPath(fullname) + source_file, pathname, description = self.FindModuleRestricted(submodule, fullname, search_path) + suffix, mode, file_type = description + module_search_path = None + if file_type == self._imp.PKG_DIRECTORY: + module_search_path = [pathname] + pathname = os.path.join(pathname, '__init__%spy' % os.extsep) + return pathname, module_search_path, submodule + + @Trace + def load_module(self, fullname): + """See PEP 302.""" + all_modules = fullname.split('.') + submodule = all_modules[-1] + parent_module_fullname = '.'.join(all_modules[:-1]) + search_path = None + if parent_module_fullname and parent_module_fullname in self._module_dict: + parent_module = self._module_dict[parent_module_fullname] + if hasattr(parent_module, '__path__'): + search_path = parent_module.__path__ + + return self.FindAndLoadModule(submodule, fullname, search_path) + + @Trace + def is_package(self, fullname): + """See PEP 302 extensions.""" + submodule, search_path = self.GetParentSearchPath(fullname) + source_file, pathname, description = self.FindModuleRestricted(submodule, fullname, search_path) + suffix, mode, file_type = description + if file_type == self._imp.PKG_DIRECTORY: + return True + return False + + @Trace + def get_source(self, fullname): + """See PEP 302 extensions.""" + full_path, search_path, submodule = self.GetModuleInfo(fullname) + if full_path is None: + return None + source_file = open(full_path) + try: + return source_file.read() + finally: + source_file.close() + + @Trace + def get_code(self, fullname): + """See PEP 302 extensions.""" + full_path, search_path, submodule = self.GetModuleInfo(fullname) + if full_path is None: + return None + source_file = open(full_path) + try: + source_code = source_file.read() + finally: + source_file.close() + + source_code = source_code.replace('\r\n', '\n') + if not source_code.endswith('\n'): + source_code += '\n' + + return compile(source_code, full_path, 'exec') + + + +def ModuleHasValidMainFunction(module): + """Determines if a module has a main function that takes no arguments. + + This includes functions that have arguments with defaults that are all + assigned, thus requiring no additional arguments in order to be called. + + Args: + module: A types.ModuleType instance. + + Returns: + True if the module has a valid, reusable main function; False otherwise. + """ + if hasattr(module, 'main') and type(module.main) is types.FunctionType: + arg_names, var_args, var_kwargs, default_values = inspect.getargspec( + module.main) + if len(arg_names) == 0: + return True + if default_values is not None and len(arg_names) == len(default_values): + return True + return False + + +def GetScriptModuleName(handler_path): + """Determines the fully-qualified Python module name of a script on disk. + + Args: + handler_path: CGI path stored in the application configuration (as a path + like 'foo/bar/baz.py'). May contain $PYTHON_LIB references. + + Returns: + String containing the corresponding module name (e.g., 'foo.bar.baz'). + """ + if handler_path.startswith(PYTHON_LIB_VAR + '/'): + handler_path = handler_path[len(PYTHON_LIB_VAR):] + handler_path = os.path.normpath(handler_path) + + extension_index = handler_path.rfind('.py') + if extension_index != -1: + handler_path = handler_path[:extension_index] + module_fullname = handler_path.replace(os.sep, '.') + module_fullname = module_fullname.strip('.') + module_fullname = re.sub('\.+', '.', module_fullname) + + if module_fullname.endswith('.__init__'): + module_fullname = module_fullname[:-len('.__init__')] + + return module_fullname + + +def FindMissingInitFiles(cgi_path, module_fullname, isfile=os.path.isfile): + """Determines which __init__.py files are missing from a module's parent + packages. + + Args: + cgi_path: Absolute path of the CGI module file on disk. + module_fullname: Fully qualified Python module name used to import the + cgi_path module. + isfile: Used for testing. + + Returns: + List containing the paths to the missing __init__.py files. + """ + missing_init_files = [] + + if cgi_path.endswith('.py'): + module_base = os.path.dirname(cgi_path) + else: + module_base = cgi_path + + depth_count = module_fullname.count('.') + if cgi_path.endswith('__init__.py') or not cgi_path.endswith('.py'): + depth_count += 1 + + for index in xrange(depth_count): + current_init_file = os.path.abspath( + os.path.join(module_base, '__init__.py')) + + if not isfile(current_init_file): + missing_init_files.append(current_init_file) + + module_base = os.path.abspath(os.path.join(module_base, os.pardir)) + + return missing_init_files + + +def LoadTargetModule(handler_path, + cgi_path, + import_hook, + module_dict=sys.modules): + """Loads a target CGI script by importing it as a Python module. + + If the module for the target CGI script has already been loaded before, + the new module will be loaded in its place using the same module object, + possibly overwriting existing module attributes. + + Args: + handler_path: CGI path stored in the application configuration (as a path + like 'foo/bar/baz.py'). Should not have $PYTHON_LIB references. + cgi_path: Absolute path to the CGI script file on disk. + import_hook: Instance of HardenedModulesHook to use for module loading. + module_dict: Used for dependency injection. + + Returns: + Tuple (module_fullname, script_module, module_code) where: + module_fullname: Fully qualified module name used to import the script. + script_module: The ModuleType object corresponding to the module_fullname. + If the module has not already been loaded, this will be an empty + shell of a module. + module_code: Code object (returned by compile built-in) corresponding + to the cgi_path to run. If the script_module was previously loaded + and has a main() function that can be reused, this will be None. + """ + module_fullname = GetScriptModuleName(handler_path) + script_module = module_dict.get(module_fullname) + module_code = None + if script_module is not None and ModuleHasValidMainFunction(script_module): + logging.debug('Reusing main() function of module "%s"', module_fullname) + else: + if script_module is None: + script_module = imp.new_module(module_fullname) + script_module.__loader__ = import_hook + + try: + module_code = import_hook.get_code(module_fullname) + full_path, search_path, submodule = ( + import_hook.GetModuleInfo(module_fullname)) + script_module.__file__ = full_path + if search_path is not None: + script_module.__path__ = search_path + except: + exc_type, exc_value, exc_tb = sys.exc_info() + import_error_message = str(exc_type) + if exc_value: + import_error_message += ': ' + str(exc_value) + + logging.exception('Encountered error loading module "%s": %s', + module_fullname, import_error_message) + missing_inits = FindMissingInitFiles(cgi_path, module_fullname) + if missing_inits: + logging.warning('Missing package initialization files: %s', + ', '.join(missing_inits)) + else: + logging.error('Parent package initialization files are present, ' + 'but must be broken') + + independent_load_successful = True + + if not os.path.isfile(cgi_path): + independent_load_successful = False + else: + try: + source_file = open(cgi_path) + try: + module_code = compile(source_file.read(), cgi_path, 'exec') + script_module.__file__ = cgi_path + finally: + source_file.close() + + except OSError: + independent_load_successful = False + + if not independent_load_successful: + raise exc_type, exc_value, exc_tb + + module_dict[module_fullname] = script_module + + return module_fullname, script_module, module_code + +def CheckRequestSize(request_size, outfile): + """Check that request size is below the maximum size. + + Checks to see if the request size small enough for small requests. Will + write the correct error message to the response outfile if the request + is too large. + + Args: + request_size: Calculated size of request. + outfile: Response outfile. + + Returns: + True if request size is ok, else False. + """ + if request_size <= MAX_REQUEST_SIZE: + return True + else: + msg = ('HTTP request was too large: %d. The limit is: %d.' + % (request_size, MAX_REQUEST_SIZE)) + logging.error(msg) + outfile.write('Status: %d Request entity too large\r\n' + '\r\n' + '%s' % (httplib.REQUEST_ENTITY_TOO_LARGE, msg)) + return False + + +def ExecuteOrImportScript(handler_path, cgi_path, import_hook): + """Executes a CGI script by importing it as a new module. + + This possibly reuses the module's main() function if it is defined and + takes no arguments. + + Basic technique lifted from PEP 338 and Python2.5's runpy module. See: + http://www.python.org/dev/peps/pep-0338/ + + See the section entitled "Import Statements and the Main Module" to understand + why a module named '__main__' cannot do relative imports. To get around this, + the requested module's path could be added to sys.path on each request. + + Args: + handler_path: CGI path stored in the application configuration (as a path + like 'foo/bar/baz.py'). Should not have $PYTHON_LIB references. + cgi_path: Absolute path to the CGI script file on disk. + import_hook: Instance of HardenedModulesHook to use for module loading. + + Returns: + True if the response code had an error status (e.g., 404), or False if it + did not. + + Raises: + Any kind of exception that could have been raised when loading the target + module, running a target script, or executing the application code itself. + """ + module_fullname, script_module, module_code = LoadTargetModule( + handler_path, cgi_path, import_hook) + script_module.__name__ = '__main__' + sys.modules['__main__'] = script_module + try: + if module_code: + exec module_code in script_module.__dict__ + else: + script_module.main() + + sys.stdout.flush() + sys.stdout.seek(0) + try: + headers = mimetools.Message(sys.stdout) + finally: + sys.stdout.seek(0, 2) + status_header = headers.get('status') + error_response = False + if status_header: + try: + status_code = int(status_header.split(' ', 1)[0]) + error_response = status_code >= 400 + except ValueError: + error_response = True + + if not error_response: + try: + parent_package = import_hook.GetParentPackage(module_fullname) + except Exception: + parent_package = None + + if parent_package is not None: + submodule = GetSubmoduleName(module_fullname) + setattr(parent_package, submodule, script_module) + + return error_response + finally: + script_module.__name__ = module_fullname + + +def ExecuteCGI(root_path, + handler_path, + cgi_path, + env, + infile, + outfile, + module_dict, + exec_script=ExecuteOrImportScript): + """Executes Python file in this process as if it were a CGI. + + Does not return an HTTP response line. CGIs should output headers followed by + the body content. + + The modules in sys.modules should be the same before and after the CGI is + executed, with the specific exception of encodings-related modules, which + cannot be reloaded and thus must always stay in sys.modules. + + Args: + root_path: Path to the root of the application. + handler_path: CGI path stored in the application configuration (as a path + like 'foo/bar/baz.py'). May contain $PYTHON_LIB references. + cgi_path: Absolute path to the CGI script file on disk. + env: Dictionary of environment variables to use for the execution. + infile: File-like object to read HTTP request input data from. + outfile: FIle-like object to write HTTP response data to. + module_dict: Dictionary in which application-loaded modules should be + preserved between requests. This removes the need to reload modules that + are reused between requests, significantly increasing load performance. + This dictionary must be separate from the sys.modules dictionary. + exec_script: Used for dependency injection. + """ + old_module_dict = sys.modules.copy() + old_builtin = __builtin__.__dict__.copy() + old_argv = sys.argv + old_stdin = sys.stdin + old_stdout = sys.stdout + old_env = os.environ.copy() + old_cwd = os.getcwd() + old_file_type = types.FileType + reset_modules = False + + try: + ClearAllButEncodingsModules(sys.modules) + before_path = sys.path[:] + sys.modules.update(module_dict) + sys.argv = [cgi_path] + sys.stdin = cStringIO.StringIO(infile.getvalue()) + sys.stdout = outfile + os.environ.clear() + os.environ.update(env) + cgi_dir = os.path.normpath(os.path.dirname(cgi_path)) + root_path = os.path.normpath(os.path.abspath(root_path)) + if cgi_dir.startswith(root_path + os.sep): + os.chdir(cgi_dir) + else: + os.chdir(root_path) + + hook = HardenedModulesHook(sys.modules) + sys.meta_path = [hook] + if hasattr(sys, 'path_importer_cache'): + sys.path_importer_cache.clear() + + __builtin__.file = FakeFile + __builtin__.open = FakeFile + types.FileType = FakeFile + + __builtin__.buffer = NotImplementedFakeClass + + logging.debug('Executing CGI with env:\n%s', pprint.pformat(env)) + try: + reset_modules = exec_script(handler_path, cgi_path, hook) + except SystemExit, e: + logging.debug('CGI exited with status: %s', e) + except: + reset_modules = True + raise + + finally: + sys.meta_path = [] + sys.path_importer_cache.clear() + + _ClearTemplateCache(sys.modules) + + module_dict.update(sys.modules) + ClearAllButEncodingsModules(sys.modules) + sys.modules.update(old_module_dict) + + __builtin__.__dict__.update(old_builtin) + sys.argv = old_argv + sys.stdin = old_stdin + sys.stdout = old_stdout + + sys.path[:] = before_path + + os.environ.clear() + os.environ.update(old_env) + os.chdir(old_cwd) + + types.FileType = old_file_type + + +class CGIDispatcher(URLDispatcher): + """Dispatcher that executes Python CGI scripts.""" + + def __init__(self, + module_dict, + root_path, + path_adjuster, + setup_env=SetupEnvironment, + exec_cgi=ExecuteCGI, + create_logging_handler=ApplicationLoggingHandler): + """Initializer. + + Args: + module_dict: Dictionary in which application-loaded modules should be + preserved between requests. This dictionary must be separate from the + sys.modules dictionary. + path_adjuster: Instance of PathAdjuster to use for finding absolute + paths of CGI files on disk. + setup_env, exec_cgi, create_logging_handler: Used for dependency + injection. + """ + self._module_dict = module_dict + self._root_path = root_path + self._path_adjuster = path_adjuster + self._setup_env = setup_env + self._exec_cgi = exec_cgi + self._create_logging_handler = create_logging_handler + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Dispatches the Python CGI.""" + request_size = int(request.headers.get('content-length', 0)) + if not CheckRequestSize(request_size, outfile): + return + + memory_file = cStringIO.StringIO() + CopyStreamPart(request.infile, memory_file, request_size) + memory_file.seek(0) + + handler = self._create_logging_handler() + logging.getLogger().addHandler(handler) + before_level = logging.root.level + try: + env = {} + if base_env_dict: + env.update(base_env_dict) + cgi_path = self._path_adjuster.AdjustPath(request.path) + env.update(self._setup_env(cgi_path, + request.relative_url, + request.headers, + memory_file)) + self._exec_cgi(self._root_path, + request.path, + cgi_path, + env, + memory_file, + outfile, + self._module_dict) + handler.AddDebuggingConsole(request.relative_url, env, outfile) + finally: + logging.root.level = before_level + logging.getLogger().removeHandler(handler) + + def __str__(self): + """Returns a string representation of this dispatcher.""" + return 'CGI dispatcher' + + +class LocalCGIDispatcher(CGIDispatcher): + """Dispatcher that executes local functions like they're CGIs. + + The contents of sys.modules will be preserved for local CGIs running this + dispatcher, but module hardening will still occur for any new imports. Thus, + be sure that any local CGIs have loaded all of their dependent modules + _before_ they are executed. + """ + + def __init__(self, module_dict, path_adjuster, cgi_func): + """Initializer. + + Args: + module_dict: Passed to CGIDispatcher. + path_adjuster: Passed to CGIDispatcher. + cgi_func: Callable function taking no parameters that should be + executed in a CGI environment in the current process. + """ + self._cgi_func = cgi_func + + def curried_exec_script(*args, **kwargs): + cgi_func() + return False + + def curried_exec_cgi(*args, **kwargs): + kwargs['exec_script'] = curried_exec_script + return ExecuteCGI(*args, **kwargs) + + CGIDispatcher.__init__(self, + module_dict, + '', + path_adjuster, + exec_cgi=curried_exec_cgi) + + def Dispatch(self, *args, **kwargs): + """Preserves sys.modules for CGIDispatcher.Dispatch.""" + self._module_dict.update(sys.modules) + CGIDispatcher.Dispatch(self, *args, **kwargs) + + def __str__(self): + """Returns a string representation of this dispatcher.""" + return 'Local CGI dispatcher for %s' % self._cgi_func + + + +class PathAdjuster(object): + """Adjusts application file paths to paths relative to the application or + external library directories.""" + + def __init__(self, root_path): + """Initializer. + + Args: + root_path: Path to the root of the application running on the server. + """ + self._root_path = os.path.abspath(root_path) + + def AdjustPath(self, path): + """Adjusts application file paths to relative to the application. + + More precisely this method adjusts application file path to paths + relative to the application or external library directories. + + Handler paths that start with $PYTHON_LIB will be converted to paths + relative to the google directory. + + Args: + path: File path that should be adjusted. + + Returns: + The adjusted path. + """ + if path.startswith(PYTHON_LIB_VAR): + path = os.path.join(os.path.dirname(os.path.dirname(google.__file__)), + path[len(PYTHON_LIB_VAR) + 1:]) + else: + path = os.path.join(self._root_path, path) + + return path + + + +class StaticFileConfigMatcher(object): + """Keeps track of file/directory specific application configuration. + + Specifically: + - Computes mime type based on URLMap and file extension. + - Decides on cache expiration time based on URLMap and default expiration. + + To determine the mime type, we first see if there is any mime-type property + on each URLMap entry. If non is specified, we use the mimetypes module to + guess the mime type from the file path extension, and use + application/octet-stream if we can't find the mimetype. + """ + + def __init__(self, + url_map_list, + path_adjuster, + default_expiration): + """Initializer. + + Args: + url_map_list: List of appinfo.URLMap objects. + If empty or None, then we always use the mime type chosen by the + mimetypes module. + path_adjuster: PathAdjuster object used to adjust application file paths. + default_expiration: String describing default expiration time for browser + based caching of static files. If set to None this disallows any + browser caching of static content. + """ + if default_expiration is not None: + self._default_expiration = appinfo.ParseExpiration(default_expiration) + else: + self._default_expiration = None + + self._patterns = [] + + if url_map_list: + for entry in url_map_list: + handler_type = entry.GetHandlerType() + if handler_type not in (appinfo.STATIC_FILES, appinfo.STATIC_DIR): + continue + + if handler_type == appinfo.STATIC_FILES: + regex = entry.upload + '$' + else: + path = entry.static_dir + if path[-1] == '/': + path = path[:-1] + regex = re.escape(path + os.path.sep) + r'(.*)' + + try: + path_re = re.compile(regex) + except re.error, e: + raise InvalidAppConfigError('regex %s does not compile: %s' % + (regex, e)) + + if self._default_expiration is None: + expiration = 0 + elif entry.expiration is None: + expiration = self._default_expiration + else: + expiration = appinfo.ParseExpiration(entry.expiration) + + self._patterns.append((path_re, entry.mime_type, expiration)) + + def IsStaticFile(self, path): + """Tests if the given path points to a "static" file. + + Args: + path: String containing the file's path relative to the app. + + Returns: + Boolean, True if the file was configured to be static. + """ + for (path_re, _, _) in self._patterns: + if path_re.match(path): + return True + return False + + def GetMimeType(self, path): + """Returns the mime type that we should use when serving the specified file. + + Args: + path: String containing the file's path relative to the app. + + Returns: + String containing the mime type to use. Will be 'application/octet-stream' + if we have no idea what it should be. + """ + for (path_re, mimetype, unused_expiration) in self._patterns: + if mimetype is not None: + the_match = path_re.match(path) + if the_match: + return mimetype + + unused_filename, extension = os.path.splitext(path) + return mimetypes.types_map.get(extension, 'application/octet-stream') + + def GetExpiration(self, path): + """Returns the cache expiration duration to be users for the given file. + + Args: + path: String containing the file's path relative to the app. + + Returns: + Integer number of seconds to be used for browser cache expiration time. + """ + for (path_re, unused_mimetype, expiration) in self._patterns: + the_match = path_re.match(path) + if the_match: + return expiration + + return self._default_expiration or 0 + + + + +def ReadDataFile(data_path, openfile=file): + """Reads a file on disk, returning a corresponding HTTP status and data. + + Args: + data_path: Path to the file on disk to read. + openfile: Used for dependency injection. + + Returns: + Tuple (status, data) where status is an HTTP response code, and data is + the data read; will be an empty string if an error occurred or the + file was empty. + """ + status = httplib.INTERNAL_SERVER_ERROR + data = "" + + try: + data_file = openfile(data_path, 'rb') + try: + data = data_file.read() + finally: + data_file.close() + status = httplib.OK + except (OSError, IOError), e: + logging.error('Error encountered reading file "%s":\n%s', data_path, e) + if e.errno in FILE_MISSING_EXCEPTIONS: + status = httplib.NOT_FOUND + else: + status = httplib.FORBIDDEN + + return status, data + + +class FileDispatcher(URLDispatcher): + """Dispatcher that reads data files from disk.""" + + def __init__(self, + path_adjuster, + static_file_config_matcher, + read_data_file=ReadDataFile): + """Initializer. + + Args: + path_adjuster: Instance of PathAdjuster to use for finding absolute + paths of data files on disk. + static_file_config_matcher: StaticFileConfigMatcher object. + read_data_file: Used for dependency injection. + """ + self._path_adjuster = path_adjuster + self._static_file_config_matcher = static_file_config_matcher + self._read_data_file = read_data_file + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Reads the file and returns the response status and data.""" + full_path = self._path_adjuster.AdjustPath(request.path) + status, data = self._read_data_file(full_path) + content_type = self._static_file_config_matcher.GetMimeType(request.path) + expiration = self._static_file_config_matcher.GetExpiration(request.path) + + outfile.write('Status: %d\r\n' % status) + outfile.write('Content-type: %s\r\n' % content_type) + if expiration: + outfile.write('Expires: %s\r\n' + % email.Utils.formatdate(time.time() + expiration, + usegmt=True)) + outfile.write('Cache-Control: public, max-age=%i\r\n' % expiration) + outfile.write('\r\n') + outfile.write(data) + + def __str__(self): + """Returns a string representation of this dispatcher.""" + return 'File dispatcher' + + +_IGNORE_RESPONSE_HEADERS = frozenset([ + 'content-encoding', 'accept-encoding', 'transfer-encoding', + 'server', 'date', blobstore.BLOB_KEY_HEADER + ]) + + +class AppServerResponse(object): + """Development appserver response object. + + Object used to hold the full appserver response. Used as a container + that is passed through the request rewrite chain and ultimately sent + to the web client. + + Attributes: + status_code: Integer HTTP response status (e.g., 200, 302, 404, 500) + status_message: String containing an informational message about the + response code, possibly derived from the 'status' header, if supplied. + headers: mimetools.Message containing the HTTP headers of the response. + body: File-like object containing the body of the response. + large_response: Indicates that response is permitted to be larger than + MAX_RUNTIME_RESPONSE_SIZE. + """ + + __slots__ = ['status_code', + 'status_message', + 'headers', + 'body', + 'large_response'] + + def __init__(self, response_file=None, **kwds): + """Initializer. + + Args: + response_file: A file-like object that contains the full response + generated by the user application request handler. If present + the headers and body are set from this value, although the values + may be further overridden by the keyword parameters. + kwds: All keywords are mapped to attributes of AppServerResponse. + """ + self.status_code = 200 + self.status_message = 'Good to go' + self.large_response = False + + if response_file: + self.SetResponse(response_file) + else: + self.headers = mimetools.Message(cStringIO.StringIO()) + self.body = None + + for name, value in kwds.iteritems(): + setattr(self, name, value) + + def SetResponse(self, response_file): + """Sets headers and body from the response file. + + Args: + response_file: File like object to set body and headers from. + """ + self.headers = mimetools.Message(response_file) + self.body = response_file + + @property + def header_data(self): + """Get header data as a string. + + Returns: + String representation of header with line breaks cleaned up. + """ + header_list = [] + for header in self.headers.headers: + header = header.rstrip('\n\r') + header_list.append(header) + + return '\r\n'.join(header_list) + '\r\n' + + +def IgnoreHeadersRewriter(response): + """Ignore specific response headers. + + Certain response headers cannot be modified by an Application. For a + complete list of these headers please see: + + http://code.google.com/appengine/docs/webapp/responseclass.html#Disallowed_HTTP_Response_Headers + + This rewriter simply removes those headers. + """ + for h in _IGNORE_RESPONSE_HEADERS: + if h in response.headers: + del response.headers[h] + + +def ParseStatusRewriter(response): + """Parse status header, if it exists. + + Handles the server-side 'status' header, which instructs the server to change + the HTTP response code accordingly. Handles the 'location' header, which + issues an HTTP 302 redirect to the client. Also corrects the 'content-length' + header to reflect actual content length in case extra information has been + appended to the response body. + + If the 'status' header supplied by the client is invalid, this method will + set the response to a 500 with an error message as content. + """ + location_value = response.headers.getheader('location') + status_value = response.headers.getheader('status') + if status_value: + response_status = status_value + del response.headers['status'] + elif location_value: + response_status = '%d Redirecting' % httplib.FOUND + else: + return response + + status_parts = response_status.split(' ', 1) + response.status_code, response.status_message = (status_parts + [''])[:2] + try: + response.status_code = int(response.status_code) + except ValueError: + response.status_code = 500 + response.body = cStringIO.StringIO( + 'Error: Invalid "status" header value returned.') + + +def CacheRewriter(response): + """Update the cache header.""" + if not 'Cache-Control' in response.headers: + response.headers['Cache-Control'] = 'no-cache' + if not 'Expires' in response.headers: + response.headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT' + + +def ContentLengthRewriter(response): + """Rewrite the Content-Length header. + + Even though Content-Length is not a user modifiable header, App Engine + sends a correct Content-Length to the user based on the actual response. + """ + current_position = response.body.tell() + response.body.seek(0, 2) + + response.headers['Content-Length'] = str(response.body.tell() - + current_position) + response.body.seek(current_position) + + +def CreateResponseRewritersChain(): + """Create the default response rewriter chain. + + A response rewriter is the a function that gets a final chance to change part + of the dev_appservers response. A rewriter is not like a dispatcher in that + it is called after every request has been handled by the dispatchers + regardless of which dispatcher was used. + + The order in which rewriters are registered will be the order in which they + are used to rewrite the response. Modifications from earlier rewriters + are used as input to later rewriters. + + A response rewriter is a function that can rewrite the request in any way. + Thefunction can returned modified values or the original values it was + passed. + + A rewriter function has the following parameters and return values: + + Args: + status_code: Status code of response from dev_appserver or previous + rewriter. + status_message: Text corresponding to status code. + headers: mimetools.Message instance with parsed headers. NOTE: These + headers can contain its own 'status' field, but the default + dev_appserver implementation will remove this. Future rewriters + should avoid re-introducing the status field and return new codes + instead. + body: File object containing the body of the response. This position of + this file may not be at the start of the file. Any content before the + files position is considered not to be part of the final body. + + Returns: + An AppServerResponse instance. + + Returns: + List of response rewriters. + """ + rewriters = [dev_appserver_blobstore.DownloadRewriter, + IgnoreHeadersRewriter, + ParseStatusRewriter, + CacheRewriter, + ContentLengthRewriter, + ] + return rewriters + + + +def RewriteResponse(response_file, + response_rewriters=None, + request_headers=None): + """Allows final rewrite of dev_appserver response. + + This function receives the unparsed HTTP response from the application + or internal handler, parses out the basic structure and feeds that structure + in to a chain of response rewriters. + + It also makes sure the final HTTP headers are properly terminated. + + For more about response rewriters, please see documentation for + CreateResponeRewritersChain. + + Args: + response_file: File-like object containing the full HTTP response including + the response code, all headers, and the request body. + response_rewriters: A list of response rewriters. If none is provided it + will create a new chain using CreateResponseRewritersChain. + request_headers: Original request headers. + + Returns: + An AppServerResponse instance configured with the rewritten response. + """ + if response_rewriters is None: + response_rewriters = CreateResponseRewritersChain() + + response = AppServerResponse(response_file) + for response_rewriter in response_rewriters: + if response_rewriter.func_code.co_argcount == 1: + response_rewriter(response) + else: + response_rewriter(response, request_headers) + + return response + + + +class ModuleManager(object): + """Manages loaded modules in the runtime. + + Responsible for monitoring and reporting about file modification times. + Modules can be loaded from source or precompiled byte-code files. When a + file has source code, the ModuleManager monitors the modification time of + the source file even if the module itself is loaded from byte-code. + """ + + def __init__(self, modules): + """Initializer. + + Args: + modules: Dictionary containing monitored modules. + """ + self._modules = modules + self._default_modules = self._modules.copy() + self._save_path_hooks = sys.path_hooks[:] + self._modification_times = {} + + @staticmethod + def GetModuleFile(module, is_file=os.path.isfile): + """Helper method to try to determine modules source file. + + Args: + module: Module object to get file for. + is_file: Function used to determine if a given path is a file. + + Returns: + Path of the module's corresponding Python source file if it exists, or + just the module's compiled Python file. If the module has an invalid + __file__ attribute, None will be returned. + """ + module_file = getattr(module, '__file__', None) + if module_file is None: + return None + + source_file = module_file[:module_file.rfind('py') + 2] + + if is_file(source_file): + return source_file + return module.__file__ + + def AreModuleFilesModified(self): + """Determines if any monitored files have been modified. + + Returns: + True if one or more files have been modified, False otherwise. + """ + for name, (mtime, fname) in self._modification_times.iteritems(): + if name not in self._modules: + continue + + module = self._modules[name] + + if not os.path.isfile(fname): + return True + + if mtime != os.path.getmtime(fname): + return True + + return False + + def UpdateModuleFileModificationTimes(self): + """Records the current modification times of all monitored modules.""" + self._modification_times.clear() + for name, module in self._modules.items(): + if not isinstance(module, types.ModuleType): + continue + module_file = self.GetModuleFile(module) + if not module_file: + continue + try: + self._modification_times[name] = (os.path.getmtime(module_file), + module_file) + except OSError, e: + if e.errno not in FILE_MISSING_EXCEPTIONS: + raise e + + def ResetModules(self): + """Clear modules so that when request is run they are reloaded.""" + self._modules.clear() + self._modules.update(self._default_modules) + sys.path_hooks[:] = self._save_path_hooks + apiproxy_stub_map.apiproxy.GetPreCallHooks().Clear() + apiproxy_stub_map.apiproxy.GetPostCallHooks().Clear() + + + + +def _ClearTemplateCache(module_dict=sys.modules): + """Clear template cache in webapp.template module. + + Attempts to load template module. Ignores failure. If module loads, the + template cache is cleared. + + Args: + module_dict: Used for dependency injection. + """ + template_module = module_dict.get('google.appengine.ext.webapp.template') + if template_module is not None: + template_module.template_cache.clear() + + + +def CreateRequestHandler(root_path, + login_url, + require_indexes=False, + static_caching=True): + """Creates a new BaseHTTPRequestHandler sub-class. + + This class will be used with the Python BaseHTTPServer module's HTTP server. + + Python's built-in HTTP server does not support passing context information + along to instances of its request handlers. This function gets around that + by creating a sub-class of the handler in a closure that has access to + this context information. + + Args: + root_path: Path to the root of the application running on the server. + login_url: Relative URL which should be used for handling user logins. + require_indexes: True if index.yaml is read-only gospel; default False. + static_caching: True if browser caching of static files should be allowed. + + Returns: + Sub-class of BaseHTTPRequestHandler. + """ + application_module_dict = SetupSharedModules(sys.modules) + + if require_indexes: + index_yaml_updater = None + else: + index_yaml_updater = dev_appserver_index.IndexYamlUpdater(root_path) + + application_config_cache = AppConfigCache() + + class DevAppServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + """Dispatches URLs using patterns from a URLMatcher. + + The URLMatcher is created by loading an application's configuration file. + Executes CGI scripts in the local process so the scripts can use mock + versions of APIs. + + HTTP requests that correctly specify a user info cookie + (dev_appserver_login.COOKIE_NAME) will have the 'USER_EMAIL' environment + variable set accordingly. If the user is also an admin, the + 'USER_IS_ADMIN' variable will exist and be set to '1'. If the user is not + logged in, 'USER_EMAIL' will be set to the empty string. + + On each request, raises an InvalidAppConfigError exception if the + application configuration file in the directory specified by the root_path + argument is invalid. + """ + server_version = 'Development/1.0' + + module_dict = application_module_dict + module_manager = ModuleManager(application_module_dict) + + config_cache = application_config_cache + + rewriter_chain = CreateResponseRewritersChain() + + def __init__(self, *args, **kwargs): + """Initializer. + + Args: + args: Positional arguments passed to the superclass constructor. + kwargs: Keyword arguments passed to the superclass constructor. + """ + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + + def version_string(self): + """Returns server's version string used for Server HTTP header.""" + return self.server_version + + def do_GET(self): + """Handle GET requests.""" + self._HandleRequest() + + def do_POST(self): + """Handles POST requests.""" + self._HandleRequest() + + def do_PUT(self): + """Handle PUT requests.""" + self._HandleRequest() + + def do_HEAD(self): + """Handle HEAD requests.""" + self._HandleRequest() + + def do_OPTIONS(self): + """Handles OPTIONS requests.""" + self._HandleRequest() + + def do_DELETE(self): + """Handle DELETE requests.""" + self._HandleRequest() + + def do_TRACE(self): + """Handles TRACE requests.""" + self._HandleRequest() + + def _Dispatch(self, dispatcher, socket_infile, outfile, env_dict): + """Copy request data from socket and dispatch. + + Args: + dispatcher: Dispatcher to handle request (MatcherDispatcher). + socket_infile: Original request file stream. + outfile: Output file to write response to. + env_dict: Environment dictionary. + """ + request_descriptor, request_file_name = tempfile.mkstemp('.tmp', + 'request.') + + try: + request_file = os.fdopen(request_descriptor, 'wb') + try: + CopyStreamPart(self.rfile, + request_file, + int(self.headers.get('content-length', 0))) + finally: + request_file.close() + + request_file = open(request_file_name, 'rb') + try: + app_server_request = AppServerRequest(self.path, + None, + self.headers, + request_file) + dispatcher.Dispatch(app_server_request, + outfile, + base_env_dict=env_dict) + finally: + request_file.close() + finally: + try: + os.remove(request_file_name) + except OSError, err: + if err.errno != errno.ENOENT: + raise + + def _HandleRequest(self): + """Handles any type of request and prints exceptions if they occur.""" + server_name = self.headers.get('host') or self.server.server_name + server_name = server_name.split(':', 1)[0] + + env_dict = { + 'REQUEST_METHOD': self.command, + 'REMOTE_ADDR': self.client_address[0], + 'SERVER_SOFTWARE': self.server_version, + 'SERVER_NAME': server_name, + 'SERVER_PROTOCOL': self.protocol_version, + 'SERVER_PORT': str(self.server.server_port), + } + + full_url = GetFullURL(server_name, self.server.server_port, self.path) + if len(full_url) > MAX_URL_LENGTH: + msg = 'Requested URI too long: %s' % full_url + logging.error(msg) + self.send_response(httplib.REQUEST_URI_TOO_LONG, msg) + return + + tbhandler = cgitb.Hook(file=self.wfile).handle + try: + if self.module_manager.AreModuleFilesModified(): + self.module_manager.ResetModules() + + implicit_matcher = CreateImplicitMatcher(self.module_dict, + root_path, + login_url) + config, explicit_matcher = LoadAppConfig(root_path, self.module_dict, + cache=self.config_cache, + static_caching=static_caching) + if config.api_version != API_VERSION: + logging.error( + "API versions cannot be switched dynamically: %r != %r", + config.api_version, API_VERSION) + sys.exit(1) + env_dict['CURRENT_VERSION_ID'] = config.version + ".1" + env_dict['APPLICATION_ID'] = config.application + dispatcher = MatcherDispatcher(login_url, + [implicit_matcher, explicit_matcher]) + + if require_indexes: + dev_appserver_index.SetupIndexes(config.application, root_path) + + outfile = cStringIO.StringIO() + try: + self._Dispatch(dispatcher, self.rfile, outfile, env_dict) + finally: + self.module_manager.UpdateModuleFileModificationTimes() + + outfile.flush() + outfile.seek(0) + + response = RewriteResponse(outfile, self.rewriter_chain, self.headers) + + if not response.large_response: + position = response.body.tell() + response.body.seek(0, 2) + end = response.body.tell() + response.body.seek(position) + runtime_response_size = end - position + + if runtime_response_size > MAX_RUNTIME_RESPONSE_SIZE: + response.status_code = 500 + response.status_message = 'Forbidden' + if 'content-length' in response.headers: + del response.headers['content-length'] + new_response = ('HTTP response was too large: %d. ' + 'The limit is: %d.' + % (runtime_response_size, + MAX_RUNTIME_RESPONSE_SIZE)) + response.headers['content-length'] = str(len(new_response)) + response.body = cStringIO.StringIO(new_response) + + except yaml_errors.EventListenerError, e: + title = 'Fatal error when loading application configuration' + msg = '%s:\n%s' % (title, str(e)) + logging.error(msg) + self.send_response(httplib.INTERNAL_SERVER_ERROR, title) + self.wfile.write('Content-Type: text/html\r\n\r\n') + self.wfile.write('
%s
' % cgi.escape(msg)) + except KeyboardInterrupt, e: + logging.info('Server interrupted by user, terminating') + self.server.stop_serving_forever() + except: + msg = 'Exception encountered handling request' + logging.exception(msg) + self.send_response(httplib.INTERNAL_SERVER_ERROR, msg) + tbhandler() + else: + try: + self.send_response(response.status_code, response.status_message) + self.wfile.write(response.header_data) + self.wfile.write('\r\n') + if self.command != 'HEAD': + shutil.copyfileobj(response.body, self.wfile, COPY_BLOCK_SIZE) + elif response.body: + logging.warning('Dropping unexpected body in response ' + 'to HEAD request') + except (IOError, OSError), e: + if e.errno != errno.EPIPE: + raise e + except socket.error, e: + if len(e.args) >= 1 and e.args[0] != errno.EPIPE: + raise e + else: + if index_yaml_updater is not None: + index_yaml_updater.UpdateIndexYaml() + + def log_error(self, format, *args): + """Redirect error messages through the logging module.""" + logging.error(format, *args) + + def log_message(self, format, *args): + """Redirect log messages through the logging module.""" + logging.info(format, *args) + + return DevAppServerRequestHandler + + + +def ReadAppConfig(appinfo_path, parse_app_config=appinfo.LoadSingleAppInfo): + """Reads app.yaml file and returns its app id and list of URLMap instances. + + Args: + appinfo_path: String containing the path to the app.yaml file. + parse_app_config: Used for dependency injection. + + Returns: + AppInfoExternal instance. + + Raises: + If the config file could not be read or the config does not contain any + URLMap instances, this function will raise an InvalidAppConfigError + exception. + """ + try: + appinfo_file = file(appinfo_path, 'r') + except IOError, unused_e: + raise InvalidAppConfigError( + 'Application configuration could not be read from "%s"' % appinfo_path) + try: + return parse_app_config(appinfo_file) + finally: + appinfo_file.close() + + +def CreateURLMatcherFromMaps(root_path, + url_map_list, + module_dict, + default_expiration, + create_url_matcher=URLMatcher, + create_cgi_dispatcher=CGIDispatcher, + create_file_dispatcher=FileDispatcher, + create_path_adjuster=PathAdjuster, + normpath=os.path.normpath): + """Creates a URLMatcher instance from URLMap. + + Creates all of the correct URLDispatcher instances to handle the various + content types in the application configuration. + + Args: + root_path: Path to the root of the application running on the server. + url_map_list: List of appinfo.URLMap objects to initialize this + matcher with. Can be an empty list if you would like to add patterns + manually. + module_dict: Dictionary in which application-loaded modules should be + preserved between requests. This dictionary must be separate from the + sys.modules dictionary. + default_expiration: String describing default expiration time for browser + based caching of static files. If set to None this disallows any + browser caching of static content. + create_url_matcher: Used for dependency injection. + create_cgi_dispatcher: Used for dependency injection. + create_file_dispatcher: Used for dependency injection. + create_path_adjuster: Used for dependency injection. + normpath: Used for dependency injection. + + Returns: + Instance of URLMatcher with the supplied URLMap objects properly loaded. + + Raises: + InvalidAppConfigError: if the handler in url_map_list is an unknown type. + """ + url_matcher = create_url_matcher() + path_adjuster = create_path_adjuster(root_path) + cgi_dispatcher = create_cgi_dispatcher(module_dict, root_path, path_adjuster) + static_file_config_matcher = StaticFileConfigMatcher(url_map_list, + path_adjuster, + default_expiration) + file_dispatcher = create_file_dispatcher(path_adjuster, + static_file_config_matcher) + + FakeFile.SetStaticFileConfigMatcher(static_file_config_matcher) + + for url_map in url_map_list: + admin_only = url_map.login == appinfo.LOGIN_ADMIN + requires_login = url_map.login == appinfo.LOGIN_REQUIRED or admin_only + auth_fail_action = url_map.auth_fail_action + + handler_type = url_map.GetHandlerType() + if handler_type == appinfo.HANDLER_SCRIPT: + dispatcher = cgi_dispatcher + elif handler_type in (appinfo.STATIC_FILES, appinfo.STATIC_DIR): + dispatcher = file_dispatcher + else: + raise InvalidAppConfigError('Unknown handler type "%s"' % handler_type) + + regex = url_map.url + path = url_map.GetHandler() + if handler_type == appinfo.STATIC_DIR: + if regex[-1] == r'/': + regex = regex[:-1] + if path[-1] == os.path.sep: + path = path[:-1] + regex = '/'.join((re.escape(regex), '(.*)')) + if os.path.sep == '\\': + backref = r'\\1' + else: + backref = r'\1' + path = (normpath(path).replace('\\', '\\\\') + + os.path.sep + backref) + + url_matcher.AddURL(regex, + dispatcher, + path, + requires_login, admin_only, auth_fail_action) + + return url_matcher + + +class AppConfigCache(object): + """Cache used by LoadAppConfig. + + If given to LoadAppConfig instances of this class are used to cache contents + of the app config (app.yaml or app.yml) and the Matcher created from it. + + Code outside LoadAppConfig should treat instances of this class as opaque + objects and not access its members. + """ + + path = None + mtime = None + config = None + matcher = None + + +def LoadAppConfig(root_path, + module_dict, + cache=None, + static_caching=True, + read_app_config=ReadAppConfig, + create_matcher=CreateURLMatcherFromMaps): + """Creates a Matcher instance for an application configuration file. + + Raises an InvalidAppConfigError exception if there is anything wrong with + the application configuration file. + + Args: + root_path: Path to the root of the application to load. + module_dict: Dictionary in which application-loaded modules should be + preserved between requests. This dictionary must be separate from the + sys.modules dictionary. + cache: Instance of AppConfigCache or None. + static_caching: True if browser caching of static files should be allowed. + read_app_config: Used for dependency injection. + create_matcher: Used for dependency injection. + + Returns: + tuple: (AppInfoExternal, URLMatcher) + + Raises: + AppConfigNotFound: if an app.yaml file cannot be found. + """ + for appinfo_path in [os.path.join(root_path, 'app.yaml'), + os.path.join(root_path, 'app.yml')]: + + if os.path.isfile(appinfo_path): + if cache is not None: + mtime = os.path.getmtime(appinfo_path) + if cache.path == appinfo_path and cache.mtime == mtime: + return (cache.config, cache.matcher) + + cache.config = cache.matcher = cache.path = None + cache.mtime = mtime + + try: + config = read_app_config(appinfo_path, appinfo.LoadSingleAppInfo) + + if static_caching: + if config.default_expiration: + default_expiration = config.default_expiration + else: + default_expiration = '0' + else: + default_expiration = None + + matcher = create_matcher(root_path, + config.handlers, + module_dict, + default_expiration) + + FakeFile.SetSkippedFiles(config.skip_files) + + if cache is not None: + cache.path = appinfo_path + cache.config = config + cache.matcher = matcher + + return (config, matcher) + except gexcept.AbstractMethod: + pass + + raise AppConfigNotFoundError + + +def ReadCronConfig(croninfo_path, parse_cron_config=croninfo.LoadSingleCron): + """Reads cron.yaml file and returns a list of CronEntry instances. + + Args: + croninfo_path: String containing the path to the cron.yaml file. + parse_cron_config: Used for dependency injection. + + Returns: + A CronInfoExternal object. + + Raises: + If the config file is unreadable, empty or invalid, this function will + raise an InvalidAppConfigError or a MalformedCronConfiguration exception. + """ + try: + croninfo_file = file(croninfo_path, 'r') + except IOError, e: + raise InvalidAppConfigError( + 'Cron configuration could not be read from "%s": %s' + % (croninfo_path, e)) + try: + return parse_cron_config(croninfo_file) + finally: + croninfo_file.close() + + + +def SetupStubs(app_id, **config): + """Sets up testing stubs of APIs. + + Args: + app_id: Application ID being served. + config: keyword arguments. + + Keywords: + root_path: Root path to the directory of the application which should + contain the app.yaml, indexes.yaml, and queues.yaml files. + login_url: Relative URL which should be used for handling user login/logout. + blobstore_path: Path to the directory to store Blobstore blobs in. + datastore_path: Path to the file to store Datastore file stub data in. + use_sqlite: Use the SQLite stub for the datastore. + history_path: DEPRECATED, No-op. + clear_datastore: If the datastore should be cleared on startup. + smtp_host: SMTP host used for sending test mail. + smtp_port: SMTP port. + smtp_user: SMTP user. + smtp_password: SMTP password. + enable_sendmail: Whether to use sendmail as an alternative to SMTP. + show_mail_body: Whether to log the body of emails. + remove: Used for dependency injection. + disable_task_running: True if tasks should not automatically run after + they are enqueued. + task_retry_seconds: How long to wait after an auto-running task before it + is tried again. + trusted: True if this app can access data belonging to other apps. This + behavior is different from the real app server and should be left False + except for advanced uses of dev_appserver. + port: The port that this dev_appserver is bound to. Defaults to 8080 + address: The host that this dev_appsever is running on. Defaults to + localhost. + """ + root_path = config.get('root_path', None) + login_url = config['login_url'] + blobstore_path = config['blobstore_path'] + datastore_path = config['datastore_path'] + clear_datastore = config['clear_datastore'] + use_sqlite = config.get('use_sqlite', False) + require_indexes = config.get('require_indexes', False) + smtp_host = config.get('smtp_host', None) + smtp_port = config.get('smtp_port', 25) + smtp_user = config.get('smtp_user', '') + smtp_password = config.get('smtp_password', '') + enable_sendmail = config.get('enable_sendmail', False) + show_mail_body = config.get('show_mail_body', False) + remove = config.get('remove', os.remove) + disable_task_running = config.get('disable_task_running', False) + task_retry_seconds = config.get('task_retry_seconds', 30) + trusted = config.get('trusted', False) + serve_port = config.get('port', 8080) + serve_address = config.get('address', 'localhost') + + os.environ['APPLICATION_ID'] = app_id + + if clear_datastore: + path = datastore_path + if os.path.lexists(path): + logging.info('Attempting to remove file at %s', path) + try: + remove(path) + except OSError, e: + logging.warning('Removing file failed: %s', e) + + apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() + + if use_sqlite: + datastore = datastore_sqlite_stub.DatastoreSqliteStub( + app_id, datastore_path, require_indexes=require_indexes, + trusted=trusted) + else: + datastore = datastore_file_stub.DatastoreFileStub( + app_id, datastore_path, require_indexes=require_indexes, + trusted=trusted) + apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore) + + fixed_login_url = '%s?%s=%%s' % (login_url, + dev_appserver_login.CONTINUE_PARAM) + fixed_logout_url = '%s&%s' % (fixed_login_url, + dev_appserver_login.LOGOUT_PARAM) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'user', + user_service_stub.UserServiceStub(login_url=fixed_login_url, + logout_url=fixed_logout_url)) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'urlfetch', + urlfetch_stub.URLFetchServiceStub()) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'mail', + mail_stub.MailServiceStub(smtp_host, + smtp_port, + smtp_user, + smtp_password, + enable_sendmail=enable_sendmail, + show_mail_body=show_mail_body)) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'memcache', + memcache_stub.MemcacheServiceStub()) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'capability_service', + capability_stub.CapabilityServiceStub()) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'taskqueue', + taskqueue_stub.TaskQueueServiceStub( + root_path=root_path, + auto_task_running=(not disable_task_running), + task_retry_seconds=task_retry_seconds)) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'xmpp', + xmpp_service_stub.XmppServiceStub()) + + apiproxy_stub_map.apiproxy.RegisterStub( + 'channel', + channel_service_stub.ChannelServiceStub()) + + + + + try: + from google.appengine.api.images import images_stub + host_prefix = 'http://%s:%d' % (serve_address, serve_port) + apiproxy_stub_map.apiproxy.RegisterStub( + 'images', + images_stub.ImagesServiceStub(host_prefix=host_prefix)) + except ImportError, e: + logging.warning('Could not initialize images API; you are likely missing ' + 'the Python "PIL" module. ImportError: %s', e) + from google.appengine.api.images import images_not_implemented_stub + apiproxy_stub_map.apiproxy.RegisterStub( + 'images', + images_not_implemented_stub.ImagesNotImplementedServiceStub()) + + blob_storage = file_blob_storage.FileBlobStorage(blobstore_path, app_id) + apiproxy_stub_map.apiproxy.RegisterStub( + 'blobstore', + blobstore_stub.BlobstoreServiceStub(blob_storage)) + + +def CreateImplicitMatcher( + module_dict, + root_path, + login_url, + create_path_adjuster=PathAdjuster, + create_local_dispatcher=LocalCGIDispatcher, + create_cgi_dispatcher=CGIDispatcher, + get_blob_storage=dev_appserver_blobstore.GetBlobStorage): + """Creates a URLMatcher instance that handles internal URLs. + + Used to facilitate handling user login/logout, debugging, info about the + currently running app, etc. + + Args: + module_dict: Dictionary in the form used by sys.modules. + root_path: Path to the root of the application. + login_url: Relative URL which should be used for handling user login/logout. + create_path_adjuster: Used for dependedency injection. + create_local_dispatcher: Used for dependency injection. + create_cgi_dispatcher: Used for dependedency injection. + get_blob_storage: Used for dependency injection. + + Returns: + Instance of URLMatcher with appropriate dispatchers. + """ + url_matcher = URLMatcher() + path_adjuster = create_path_adjuster(root_path) + + login_dispatcher = create_local_dispatcher(sys.modules, path_adjuster, + dev_appserver_login.main) + url_matcher.AddURL(login_url, + login_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_REDIRECT) + + admin_dispatcher = create_cgi_dispatcher(module_dict, root_path, + path_adjuster) + url_matcher.AddURL('/_ah/admin(?:/.*)?', + admin_dispatcher, + DEVEL_CONSOLE_PATH, + False, + False, + appinfo.AUTH_FAIL_ACTION_REDIRECT) + + upload_dispatcher = dev_appserver_blobstore.CreateUploadDispatcher( + get_blob_storage) + + url_matcher.AddURL(dev_appserver_blobstore.UPLOAD_URL_PATTERN, + upload_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED) + + blobimage_dispatcher = dev_appserver_blobimage.CreateBlobImageDispatcher( + apiproxy_stub_map.apiproxy.GetStub('images')) + url_matcher.AddURL(dev_appserver_blobimage.BLOBIMAGE_URL_PATTERN, + blobimage_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED) + + oauth_dispatcher = dev_appserver_oauth.CreateOAuthDispatcher() + + url_matcher.AddURL(dev_appserver_oauth.OAUTH_URL_PATTERN, + oauth_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED) + + channel_dispatcher = dev_appserver_channel.CreateChannelDispatcher( + apiproxy_stub_map.apiproxy.GetStub('channel')) + + url_matcher.AddURL('/_ah/channel/dev(?:/.*)?', + channel_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED) + + url_matcher.AddURL('/_ah/channel/jsapi', + channel_dispatcher, + '', + False, + False, + appinfo.AUTH_FAIL_ACTION_UNAUTHORIZED) + + + return url_matcher + + +def SetupTemplates(template_dir): + """Reads debugging console template files and initializes the console. + + Does nothing if templates have already been initialized. + + Args: + template_dir: Path to the directory containing the templates files. + + Raises: + OSError or IOError if any of the template files could not be read. + """ + if ApplicationLoggingHandler.AreTemplatesInitialized(): + return + + try: + header = open(os.path.join(template_dir, HEADER_TEMPLATE)).read() + script = open(os.path.join(template_dir, SCRIPT_TEMPLATE)).read() + middle = open(os.path.join(template_dir, MIDDLE_TEMPLATE)).read() + footer = open(os.path.join(template_dir, FOOTER_TEMPLATE)).read() + except (OSError, IOError): + logging.error('Could not read template files from %s', template_dir) + raise + + ApplicationLoggingHandler.InitializeTemplates(header, script, middle, footer) + + +def CreateServer(root_path, + login_url, + port, + template_dir, + serve_address='', + require_indexes=False, + allow_skipped_files=False, + static_caching=True, + python_path_list=sys.path, + sdk_dir=os.path.dirname(os.path.dirname(google.__file__))): + """Creates an new HTTPServer for an application. + + The sdk_dir argument must be specified for the directory storing all code for + the SDK so as to allow for the sandboxing of module access to work for any + and all SDK code. While typically this is where the 'google' package lives, + it can be in another location because of API version support. + + Args: + root_path: String containing the path to the root directory of the + application where the app.yaml file is. + login_url: Relative URL which should be used for handling user login/logout. + port: Port to start the application server on. + template_dir: Path to the directory in which the debug console templates + are stored. + serve_address: Address on which the server should serve. + require_indexes: True if index.yaml is read-only gospel; default False. + allow_skipped_files: True if skipped files should be accessible. + static_caching: True if browser caching of static files should be allowed. + python_path_list: Used for dependency injection. + sdk_dir: Directory where the SDK is stored. + + Returns: + Instance of BaseHTTPServer.HTTPServer that's ready to start accepting. + """ + absolute_root_path = os.path.realpath(root_path) + + SetupTemplates(template_dir) + FakeFile.SetAllowedPaths(absolute_root_path, + [sdk_dir, + template_dir]) + FakeFile.SetAllowSkippedFiles(allow_skipped_files) + + handler_class = CreateRequestHandler(absolute_root_path, + login_url, + require_indexes, + static_caching) + + if absolute_root_path not in python_path_list: + python_path_list.insert(0, absolute_root_path) + + server = HTTPServerWithScheduler((serve_address, port), handler_class) + + queue_stub = apiproxy_stub_map.apiproxy.GetStub('taskqueue') + if queue_stub: + queue_stub._add_event = server.AddEvent + + return server + + +class HTTPServerWithScheduler(BaseHTTPServer.HTTPServer): + """A BaseHTTPServer subclass that calls a method at a regular interval.""" + + def __init__(self, server_address, request_handler_class): + """Constructor. + + Args: + server_address: the bind address of the server. + request_handler_class: class used to handle requests. + """ + BaseHTTPServer.HTTPServer.__init__(self, server_address, + request_handler_class) + self._events = [] + self._stopped = False + + def get_request(self, time_func=time.time, select_func=select.select): + """Overrides the base get_request call. + + Args: + time_func: used for testing. + select_func: used for testing. + + Returns: + a (socket_object, address info) tuple. + """ + while True: + if self._events: + current_time = time_func() + next_eta = self._events[0][0] + delay = next_eta - current_time + else: + delay = DEFAULT_SELECT_DELAY + readable, _, _ = select_func([self.socket], [], [], max(delay, 0)) + if readable: + return self.socket.accept() + current_time = time_func() + if self._events and current_time >= self._events[0][0]: + unused_eta, runnable = heapq.heappop(self._events) + request_tuple = runnable() + if request_tuple: + return request_tuple + + def serve_forever(self): + """Handle one request at a time until told to stop.""" + while not self._stopped: + self.handle_request() + + def stop_serving_forever(self): + """Stop the serve_forever() loop. + + Stop happens on the next handle_request() loop; it will not stop + immediately. Since dev_appserver.py must run on py2.5 we can't + use newer features of SocketServer (e.g. shutdown(), added in py2.6). + """ + self._stopped = True + + def AddEvent(self, eta, runnable): + """Add a runnable event to be run at the specified time. + + Args: + eta: when to run the event, in seconds since epoch. + runnable: a callable object. + """ + heapq.heappush(self._events, (eta, runnable)) diff --git a/google_appengine/google/appengine/tools/dev_appserver.pyc b/google_appengine/google/appengine/tools/dev_appserver.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fc6b7a922d4b1f84b4d7831aba9d18107b6b61d GIT binary patch literal 131109 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 lvHyP41Vc}`5=SnLj>ujAM~4mM literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_blobimage.py b/google_appengine/google/appengine/tools/dev_appserver_blobimage.py new file mode 100755 index 0000000..f5837c8 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_blobimage.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Dispatcher for dynamic image serving requests. + +Classes: + + CreateBlobImageDispatcher: + Creates a dispatcher that will handle an image serving request. It will + fetch an image from blobstore and dynamically resize it. +""" + + +import re +import urlparse +import logging + +from google.appengine.api.images import images_service_pb + +BLOBIMAGE_URL_PATTERN = '/_ah/img(?:/.*)?' + +BLOBIMAGE_RESPONSE_TEMPLATE = ('Status: %(status)s\r\nContent-Type: %(content_type)s' + '\r\n\r\n%(data)s') + +def CreateBlobImageDispatcher(images_stub): + """Function to create a dynamic image serving stub. + + Args: + images_stub: an images_stub to perform the image resizing on blobs. + + + Returns: + New dispatcher capable of dynamic image serving requests. + """ + + from google.appengine.tools import dev_appserver + + class BlobImageDispatcher(dev_appserver.URLDispatcher): + """Dispatcher that handles image serving requests.""" + + _cropped_sizes = [32, 48, 64, 72, 80, 104, 136, 144, 150, 160] + _uncropped_sizes = [0, 32, 48, 64, 72, 80, 90, 94, 104, 110, 120, 128, 144, + 150, 160, 200, 220, 288, 320, 400, 512, 576, 640, 720, + 800, 912, 1024, 1152, 1280, 1440, 1600] + + def __init__(self, images_stub): + """Constructor. + + Args: + images_stub: an images_stub to perform the image resizing on blobs. + """ + self._images_stub = images_stub + + def _TransformImage(self, blob_key, options): + """Construct and execute transform request to the images stub. + + Args: + blob_key: blob_key to the image to transform. + options: resize and crop option string to apply to the image. + + Returns: + The tranformed (if necessary) image bytes. + """ + resize, crop = self._ParseOptions(options) + image_data = images_service_pb.ImageData() + image_data.set_blob_key(blob_key); + image = self._images_stub._OpenImageData(image_data) + + + if crop: + width, height = image.size + crop_xform = None + if width > height: + crop_xform = images_service_pb.Transform() + delta = (width - height) / (width * 2.0) + crop_xform.set_crop_left_x(delta) + crop_xform.set_crop_right_x(1.0 - delta) + elif width < height: + crop_xform = images_service_pb.Transform() + delta = (height - width) / (height * 2.0) + top_delta = max(0, delta - 0.25) + bottom_delta = 1.0 - (2.0 * delta) + top_delta + crop_xform.set_crop_top_y(top_delta) + crop_xform.set_crop_bottom_y(bottom_delta) + if crop_xform: + image = self._images_stub._Crop(image, crop_xform) + + if resize: + resize_xform = images_service_pb.Transform() + resize_xform.set_width(resize) + resize_xform.set_height(resize) + image = self._images_stub._Resize(image, resize_xform) + + output_settings = images_service_pb.OutputSettings() + output_settings.set_mime_type(images_service_pb.OutputSettings.JPEG) + return self._images_stub._EncodeImage(image, output_settings) + + def _ParseOptions(self, options): + """Currently only support resize and crop options. + + Args: + options: the url resize and crop option string. + + Returns: + (resize, crop) options parsed from the string. + """ + match = re.search('^s(\\d+)(-c)?', options) + resize = None + crop = False + if match: + if match.group(1): + resize = int(match.group(1)) + if match.group(2): + crop = True + + if resize: + if crop and resize not in BlobImageDispatcher._cropped_sizes: + raise ValueError, 'Invalid crop size' + elif resize not in BlobImageDispatcher._uncropped_sizes: + raise ValueError, 'Invalid resize' + return (resize, crop) + + def _ParseUrl(self, url): + """Parse the URL into the blobkey and option string. + + Args: + url: a url as a string. + + Returns: + (blob_key, option) tuple parsed out of the URL. + """ + path = urlparse.urlsplit(url)[2] + match = re.search('/_ah/img/([-\\w]+)([=]*)([-\\w]+)?', path) + if not match or not match.group(1): + raise ValueError, 'Failed to parse image url.' + options = '' + blobkey = match.group(1) + if match.group(3): + if match.group(2): + blobkey = ''.join([blobkey, match.group(2)[1:]]) + options = match.group(3) + elif match.group(2): + blobkey = ''.join([blobkey, match.group(2)]) + return (blobkey, options) + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Handle GET image serving request. + + This dispatcher handles image requests under the /_ah/img/ path. + The rest of the path should be a serialized blobkey used to retrieve + the image from blobstore. + + Args: + request: The HTTP request. + outfile: The response file. + base_env_dict: Dictionary of CGI environment parameters if available. + Defaults to None. + """ + try: + if base_env_dict and base_env_dict['REQUEST_METHOD'] != 'GET': + raise RuntimeError, 'BlobImage only handles GET requests.' + + blobkey, options = self._ParseUrl(request.relative_url); + image = self._TransformImage(blobkey, options) + output_dict = { 'status': 200, 'content_type': 'image/jpeg', + 'data': image } + outfile.write(BLOBIMAGE_RESPONSE_TEMPLATE % output_dict) + except ValueError: + outfile.write('Status: 404\r\n') + except RuntimeError: + outfile.write('Status: 400\r\n') + except: + outfile.write('Status: 500\r\n') + + return BlobImageDispatcher(images_stub) diff --git a/google_appengine/google/appengine/tools/dev_appserver_blobimage.pyc b/google_appengine/google/appengine/tools/dev_appserver_blobimage.pyc new file mode 100644 index 0000000000000000000000000000000000000000..481781f5c1783290f2461bd394124fd54706c76c GIT binary patch literal 6531 zcwW6&&2t>p5%1ZRR?_Oj@`peIj0eKU3a~_ip(=u48+);pGLkY92@xMtqn)SSF|#x4 zd2eNlk}6c70#xO|DOI`g59E|f&N-zjRSCJ}kVF22R4O?q{p&X~yRxlt30~E<_I-8t z>({^Te(x{;oUZ@rvG7KV@=uMwm-wqUnS?~!OdSd<^r1uBPVu`!+m+&XmA0$$yGkF9 zP&`W8qvV7mv|Xc+8)FpK=u>7ij@g@yF);r-ldF;L1*X+enY){2Zn&QW-Kgb8-Jq>p zt+Kr+X}g)a)mPf+rF#8J9B8fdO1Ybn3Cqi4By*$+C)QMcW=xU#(T+~P(3I(KfG!1VQs`*cIguSGpw zzmg_KCFcCreotYfW!t{tHpZvxPdCEA1dF-_krn=q^0x|ef1k=zB#Iq6bohj-bl^}_ zpC)#%|E%V^TRUu_c(XM6ozCWKNzk{Vd6uuBqF@XWR;U!44m7 zy8Dg(#0uen?DpwD4B-o$-h{2jp{*FF}3<@)Zc|7;S@m0Fr>*0r?o@4a*3l_tCF7>z6fp_ht3dSH}?e|n+-Y8mp5wx^7?|gOe1vD_4(oIHH_G)1=_mLD!l2gGqY$a7rXHoh&6z z)4`N965Ek3l>40_-XC{?SB-AH>ex9|N-qxU!W^b4Izc=jOt~>zHY}%;!f86^1TI*9 zN|v*f8H!Gm?TO9|ubY!~Gg!CTK)%;A&XCJXPE&VOtIzS>WI>|ElXIZMxl5llGz#uZ<*KxY48yrE&!kYMm8HFWfCXj%5ie$sjgPV{6*-b3@Y(9v ztw5Fv-A1(QCaR^h4zm5lT%VnNE*T0%+GIbM@*n@WDA6i!eSLAQ@et*C!`}!pt=6sH z9)`^X6Pq|VAq%p#GJatO58yE~Sh~(Y9846j^QPs;TACOg(RP6gV5yZoVf6 zBfQu9rq?%{${0QqE!M#7Zq!v$m&itbxY4|3qgyj+rJ<7jHn0~D!xCZ&(};xJ1q@u% zdV%nP#-flMmycRU^p>XlvN~f~u5UeG=HC(fVNR%GUWZ-69<~MLa#IXCtmJ$pv`uBR zT))}fKBx!`(SGRz)>$BQ1)}S7&XhCloT`pFC!8ApnsQDtoy&inmexsU%sE>blbO@b zLS>5kTf_AsY90N1n1rn2h(Fif7pT++>Djgqx8g|_avS_hj>-G>i zLVKrxgo2G-q~V4i=*D~D^NWr1t;LHG3WZ3D6u@{j*$d(*R|M?!%uuhDxYhs$Ohp`8 zprKWewK^Vn3n1PMVr}8m&eDEQVn>u1f!tQsw?LP0(Nm!>Vgy$|4&uIQW*Hk5G05k; z!SEcpH3-1H#|lwhl#||B^gYf*>K+fOefu!-;1@kefi^U8iqTSIT+BKP{GDNRJj~db zWvmD?V5qXR`8O5Fu$2qrV}YT;M&)7#l|@CsQbl^6>YWsKEFFJJ9CkGKW;iSAnGqVE zgUZM@Yl4;4l7+}ipOHme6CIQjts+%A*RJFoavYRgSVr>FU5uVQhS5q5+LC}3T{7^O2&fQq zNV?*FnN~D(FOH1&9NhVNknnMR4!SvKEQ?HU>43s%Q_i3*TFKyK3qm0pku zZ3Xjo7JpHggC>wV78nvWPAs$h_Y5bES?5Wo1+}qlY)IhoS0?*EgFmH@i@yOM42zpp z8>|U_n*07i0i&qAB#emri;3Coa2Jqrwo}}UU$HmnSGZ>7EAcCQC$M=1X=jWcLS6fl z#G?@oZa3?co*{=ndFq+4HTVT`Mp%t|hV2a@nH%n#EXwwG_)5Onkdo%t>?YUko6)uA z)-jJpg%fXeB0c;harD|;T$kN`63WF|xuui3@P?(5ja(T^|HhcBJ83@--5tEUu>6Pv z{YUJz1q}Pzx-dujEK++aXEi9fk3L=<1JYc@6;a{(*4D;gi^UYi-7e#0rO>j+$&9&k z*I>?0pp~zZJwJ?E%2-H9vpse6YQb@l{bXqweG@;7pCiX^}Kjj&&%jpvylW`y?$c2T9!QO zy;}5UDh^Dvr+khR-Xi7c^L7>)Wgf+zZmhqt`qt%ZP2X#7Zmh3uHvO&UTN^hnZ#5o{NQ(RF=FLeo*lUninrmUr~{-XRFS zzEnltYO&J?TA)D81xPu=go~5PQ_i!_6V9WRiSdaSPAp73(vWK-uAP?oQptQtn0w_* zCg;Mznc< Msmg4nR;gA03)aX*{Qv*} literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_blobstore.py b/google_appengine/google/appengine/tools/dev_appserver_blobstore.py new file mode 100755 index 0000000..d5dc772 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_blobstore.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Blobstore support classes. + +Classes: + + DownloadRewriter: + Rewriter responsible for transforming an application response to one + that serves a blob to the user. + + CreateUploadDispatcher: + Creates a dispatcher that is added to dispatcher chain. Handles uploads + by storing blobs rewriting requests and returning a redirect. +""" + + + +import cgi +import cStringIO +import logging +import mimetools +import re +import sys + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import blobstore +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.tools import dev_appserver_upload + +from webob import byterange + + +UPLOAD_URL_PATH = '_ah/upload/' + +UPLOAD_URL_PATTERN = '/%s(.*)' % UPLOAD_URL_PATH + + +def GetBlobStorage(): + """Get blob-storage from api-proxy stub map. + + Returns: + BlobStorage instance as registered with blobstore API in stub map. + """ + return apiproxy_stub_map.apiproxy.GetStub('blobstore').storage + + +def ParseRangeHeader(range_header): + """Parse HTTP Range header. + + Args: + range_header: Range header as retrived from Range or X-AppEngine-BlobRange. + + Returns: + Tuple (start, end): + start: Start index of blob to retrieve. May be negative index. + end: None or end index. End index is inclusive. + (None, None) if there is a parse error. + """ + if not range_header: + return None, None + original_stdout = sys.stdout + sys.stdout = cStringIO.StringIO() + try: + parsed_range = byterange.Range.parse_bytes(range_header) + finally: + sys.stdout = original_stdout + if parsed_range: + range_tuple = parsed_range[1] + if len(range_tuple) == 1: + return range_tuple[0] + return None, None + + +class _FixedContentRange(byterange.ContentRange): + """Corrected version of byterange.ContentRange class. + + The version of byterange.ContentRange that comes with the SDK has + a bug that has since been corrected in newer versions. It treats + content ranges as if they are specified as end-index exclusive. + Content ranges are meant to be inclusive. This sub-class partially + fixes the bug in order to allow content-range header parsing. + + The fix works by adding 1 to the stop parameter in the constructor. + This is necessary to handle content-ranges where the start index + is equal to the end index. + """ + + def __init__(self, start, stop, length): + stop = stop + 1 + super(_FixedContentRange, self).__init__(start, stop, length) + + +def ParseContentRangeHeader(content_range_header): + """Parse HTTP Content-Range header. + + Args: + content_range_header: Content-Range header. + + Returns: + Tuple (start, end): + start: Start index of blob to retrieve. May be negative index. + end: None or end index. End index is inclusive. + (None, None) if there is a parse error. + """ + if not content_range_header: + return None + parsed_content_range = _FixedContentRange.parse(content_range_header) + if parsed_content_range: + return parsed_content_range.start, parsed_content_range.stop + return None, None + + +def DownloadRewriter(response, request_headers): + """Intercepts blob download key and rewrites response with large download. + + Checks for the X-AppEngine-BlobKey header in the response. If found, it will + discard the body of the request and replace it with the blob content + indicated. + + If a valid blob is not found, it will send a 404 to the client. + + If the application itself provides a content-type header, it will override + the content-type stored in the action blob. + + If Content-Range header is provided, blob will be partially served. The + application can set blobstore.BLOB_RANGE_HEADER if the size of the blob is + not known. If Range is present, and not blobstore.BLOB_RANGE_HEADER, will + use Range instead. + + Args: + response: Response object to be rewritten. + request_headers: Original request headers. Looks for 'Range' header to copy + to response. + """ + blob_key = response.headers.getheader(blobstore.BLOB_KEY_HEADER) + if blob_key: + del response.headers[blobstore.BLOB_KEY_HEADER] + + try: + blob_info = datastore.Get( + datastore.Key.from_path(blobstore.BLOB_INFO_KIND, + blob_key, + namespace='')) + + content_range_header = response.headers.getheader('Content-Range') + blob_size = blob_info['size'] + range_header = response.headers.getheader(blobstore.BLOB_RANGE_HEADER) + if range_header is not None: + del response.headers[blobstore.BLOB_RANGE_HEADER] + else: + range_header = request_headers.getheader('Range') + + def not_satisfiable(): + """Short circuit response and return 416 error.""" + response.status_code = 416 + response.status_message = 'Requested Range Not Satisfiable' + response.body = cStringIO.StringIO('') + response.headers['Content-Length'] = '0' + del response.headers['Content-Type'] + del response.headers['Content-Range'] + + if range_header: + start, end = ParseRangeHeader(range_header) + if start is not None: + if end is None: + if start >= 0: + content_range_start = start + else: + content_range_start = blob_size + start + content_range = byterange.ContentRange( + content_range_start, blob_size - 1, blob_size) + content_range.stop -= 1 + content_range_header = str(content_range) + else: + range = byterange.ContentRange(start, end, blob_size) + range.stop -= 1 + content_range_header = str(range) + response.headers['Content-Range'] = content_range_header + else: + not_satisfiable() + return + + content_range = response.headers.getheader('Content-Range') + content_length = blob_size + start = 0 + end = content_length + if content_range is not None: + parsed_start, parsed_end = ParseContentRangeHeader(content_range) + if parsed_start is not None: + start = parsed_start + content_range = byterange.ContentRange(start, + parsed_end, + blob_size) + content_range.stop -= 1 + content_range.stop = min(content_range.stop, blob_size - 2) + content_length = min(parsed_end, blob_size - 1) - start + 1 + response.headers['Content-Range'] = str(content_range) + else: + not_satisfiable() + return + + blob_stream = GetBlobStorage().OpenBlob(blob_key) + blob_stream.seek(start) + response.body = cStringIO.StringIO(blob_stream.read(content_length)) + response.headers['Content-Length'] = str(content_length) + + if not response.headers.getheader('Content-Type'): + response.headers['Content-Type'] = blob_info['content_type'] + response.large_response = True + + except datastore_errors.EntityNotFoundError: + response.status_code = 500 + response.status_message = 'Internal Error' + response.body = cStringIO.StringIO() + + if response.headers.getheader('status'): + del response.headers['status'] + if response.headers.getheader('location'): + del response.headers['location'] + if response.headers.getheader('content-type'): + del response.headers['content-type'] + + logging.error('Could not find blob with key %s.', blob_key) + + +def CreateUploadDispatcher(get_blob_storage=GetBlobStorage): + """Function to create upload dispatcher. + + Returns: + New dispatcher capable of handling large blob uploads. + """ + from google.appengine.tools import dev_appserver + + class UploadDispatcher(dev_appserver.URLDispatcher): + """Dispatcher that handles uploads.""" + + def __init__(self): + """Constructor. + + Args: + blob_storage: A BlobStorage instance. + """ + self.__cgi_handler = dev_appserver_upload.UploadCGIHandler( + get_blob_storage()) + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Handle post dispatch. + + This dispatcher will handle all uploaded files in the POST request, store + the results in the blob-storage, close the upload session and transform + the original request in to one where the uploaded files have external + bodies. + + Returns: + New AppServerRequest indicating request forward to upload success + handler. + """ + if base_env_dict['REQUEST_METHOD'] != 'POST': + outfile.write('Status: 400\n\n') + return + + upload_key = re.match(UPLOAD_URL_PATTERN, request.relative_url).group(1) + try: + upload_session = datastore.Get(upload_key) + except datastore_errors.EntityNotFoundError: + upload_session = None + + if upload_session: + success_path = upload_session['success_path'] + + upload_form = cgi.FieldStorage(fp=request.infile, + headers=request.headers, + environ=base_env_dict) + + try: + mime_message_string = self.__cgi_handler.GenerateMIMEMessageString( + upload_form) + datastore.Delete(upload_session) + self.current_session = upload_session + + header_end = mime_message_string.find('\n\n') + 1 + content_start = header_end + 1 + header_text = mime_message_string[:header_end] + content_text = mime_message_string[content_start:] + + complete_headers = ('%s' + 'Content-Length: %d\n' + '\n') % (header_text, len(content_text)) + + return dev_appserver.AppServerRequest( + success_path, + None, + mimetools.Message(cStringIO.StringIO(complete_headers)), + cStringIO.StringIO(content_text), + force_admin=True) + except dev_appserver_upload.InvalidMIMETypeFormatError: + outfile.write('Status: 400\n\n') + else: + logging.error('Could not find session for %s', upload_key) + outfile.write('Status: 404\n\n') + + def EndRedirect(self, redirected_outfile, original_outfile): + """Handle the end of upload complete notification. + + Makes sure the application upload handler returned an appropriate status + code. + """ + response = dev_appserver.RewriteResponse(redirected_outfile) + logging.info('Upload handler returned %d', response.status_code) + + if (response.status_code in (301, 302, 303) and + (not response.body or len(response.body.read()) == 0)): + contentless_outfile = cStringIO.StringIO() + contentless_outfile.write('Status: %s\n' % response.status_code) + contentless_outfile.write(''.join(response.headers.headers)) + contentless_outfile.seek(0) + dev_appserver.URLDispatcher.EndRedirect(self, + contentless_outfile, + original_outfile) + else: + logging.error( + 'Invalid upload handler response. Only 301, 302 and 303 ' + 'statuses are permitted and it may not have a content body.') + original_outfile.write('Status: 500\n\n') + + return UploadDispatcher() diff --git a/google_appengine/google/appengine/tools/dev_appserver_blobstore.pyc b/google_appengine/google/appengine/tools/dev_appserver_blobstore.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bad7de27bfdfce9070997f770f1f60621cade0db GIT binary patch literal 11290 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_index.py b/google_appengine/google/appengine/tools/dev_appserver_index.py new file mode 100755 index 0000000..0ed7fe0 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_index.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Utilities for generating and updating index.yaml.""" + + + +import os +import logging + +from google.appengine.api import apiproxy_stub_map +from google.appengine.api import datastore_admin +from google.appengine.api import yaml_errors +from google.appengine.datastore import datastore_index + +import yaml + +AUTO_MARKER = '\n# AUTOGENERATED\n' + +AUTO_COMMENT = ''' +# This index.yaml is automatically updated whenever the dev_appserver +# detects that a new type of query is run. If you want to manage the +# index.yaml file manually, remove the above marker line (the line +# saying "# AUTOGENERATED"). If you want to manage some indexes +# manually, move them above the marker line. The index.yaml file is +# automatically uploaded to the admin console when you next deploy +# your application using appcfg.py. +''' + + +def GenerateIndexFromHistory(query_history, + all_indexes=None, manual_indexes=None): + """Generate most of the text for index.yaml from the query history. + + Args: + query_history: Query history, a dict mapping query + all_indexes: Optional datastore_index.IndexDefinitions instance + representing all the indexes found in the input file. May be None. + manual_indexes: Optional datastore_index.IndexDefinitions instance + containing indexes for which we should not generate output. May be None. + + Returns: + A string representation that can safely be appended to an existing + index.yaml file. Returns the empty string if it would generate no output. + """ + + all_keys = datastore_index.IndexDefinitionsToKeys(all_indexes) + manual_keys = datastore_index.IndexDefinitionsToKeys(manual_indexes) + + indexes = dict((key, 0) for key in all_keys - manual_keys) + + for query, count in query_history.iteritems(): + required, kind, ancestor, props, num_eq_filters = datastore_index.CompositeIndexForQuery(query) + if required: + key = (kind, ancestor, props) + if key not in manual_keys: + if key in indexes: + indexes[key] += count + else: + indexes[key] = count + + if not indexes: + return '' + + res = [] + for (kind, ancestor, props), count in sorted(indexes.iteritems()): + res.append('') + res.append(datastore_index.IndexYamlForQuery(kind, ancestor, props)) + + res.append('') + return '\n'.join(res) + + +class IndexYamlUpdater(object): + """Helper class for updating index.yaml. + + This class maintains some state about the query history and the + index.yaml file in order to minimize the number of times index.yaml + is actually overwritten. + """ + + index_yaml_is_manual = False + index_yaml_mtime = None + last_history_size = 0 + + def __init__(self, root_path): + """Constructor. + + Args: + root_path: Path to the app's root directory. + """ + self.root_path = root_path + + def UpdateIndexYaml(self, openfile=open): + """Update index.yaml. + + Args: + openfile: Used for dependency injection. + + We only ever write to index.yaml if either: + - it doesn't exist yet; or + - it contains an 'AUTOGENERATED' comment. + + All indexes *before* the AUTOGENERATED comment will be written + back unchanged. All indexes *after* the AUTOGENERATED comment + will be updated with the latest query counts (query counts are + reset by --clear_datastore). Indexes that aren't yet in the file + will be appended to the AUTOGENERATED section. + + We keep track of some data in order to avoid doing repetitive work: + - if index.yaml is fully manual, we keep track of its mtime to + avoid parsing it over and over; + - we keep track of the number of keys in the history dict since + the last time we updated index.yaml (or decided there was + nothing to update). + """ + index_yaml_file = os.path.join(self.root_path, 'index.yaml') + + try: + index_yaml_mtime = os.path.getmtime(index_yaml_file) + except os.error: + index_yaml_mtime = None + + index_yaml_changed = (index_yaml_mtime != self.index_yaml_mtime) + self.index_yaml_mtime = index_yaml_mtime + + datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3') + query_history = datastore_stub.QueryHistory() + history_changed = (len(query_history) != self.last_history_size) + self.last_history_size = len(query_history) + + if not (index_yaml_changed or history_changed): + logging.debug('No need to update index.yaml') + return + + if self.index_yaml_is_manual and not index_yaml_changed: + logging.debug('Will not update manual index.yaml') + return + + if index_yaml_mtime is None: + index_yaml_data = None + else: + try: + fh = open(index_yaml_file, 'r') + except IOError: + index_yaml_data = None + else: + try: + index_yaml_data = fh.read() + finally: + fh.close() + + self.index_yaml_is_manual = (index_yaml_data is not None and + AUTO_MARKER not in index_yaml_data) + if self.index_yaml_is_manual: + logging.info('Detected manual index.yaml, will not update') + return + + if index_yaml_data is None: + all_indexes = None + else: + try: + all_indexes = datastore_index.ParseIndexDefinitions(index_yaml_data) + except yaml_errors.EventListenerError, e: + logging.error('Error parsing %s:\n%s', index_yaml_file, e) + return + except Exception, err: + logging.error('Error parsing %s:\n%s.%s: %s', index_yaml_file, + err.__class__.__module__, err.__class__.__name__, err) + return + + if index_yaml_data is None: + manual_part, automatic_part = 'indexes:\n', '' + manual_indexes = None + else: + manual_part, automatic_part = index_yaml_data.split(AUTO_MARKER, 1) + try: + manual_indexes = datastore_index.ParseIndexDefinitions(manual_part) + except Exception, err: + logging.error('Error parsing manual part of %s: %s', + index_yaml_file, err) + return + + automatic_part = GenerateIndexFromHistory(query_history, + all_indexes, manual_indexes) + + if index_yaml_mtime is None and automatic_part == '': + logging.debug('No need to update index.yaml') + return + + try: + fh = openfile(index_yaml_file, 'w') + except IOError, err: + logging.error('Can\'t write index.yaml: %s', err) + return + + try: + logging.info('Updating %s', index_yaml_file) + fh.write(manual_part) + fh.write(AUTO_MARKER) + fh.write(AUTO_COMMENT) + fh.write(automatic_part) + finally: + fh.close() + + try: + self.index_yaml_mtime = os.path.getmtime(index_yaml_file) + except os.error, err: + logging.error('Can\'t stat index.yaml we just wrote: %s', err) + self.index_yaml_mtime = None + + +def SetupIndexes(app_id, root_path): + """Ensure that the set of existing composite indexes matches index.yaml. + + Note: this is similar to the algorithm used by the admin console for + the same purpose. + + Args: + app_id: Application ID being served. + root_path: Path to the root of the application. + """ + index_yaml_file = os.path.join(root_path, 'index.yaml') + try: + fh = open(index_yaml_file, 'r') + except IOError: + index_yaml_data = None + else: + try: + index_yaml_data = fh.read() + finally: + fh.close() + + indexes = [] + if index_yaml_data is not None: + index_defs = datastore_index.ParseIndexDefinitions(index_yaml_data) + if index_defs is not None: + indexes = index_defs.indexes + if indexes is None: + indexes = [] + + requested_indexes = datastore_index.IndexDefinitionsToProtos(app_id, indexes) + + existing_indexes = datastore_admin.GetIndices(app_id) + + requested = dict((x.definition().Encode(), x) for x in requested_indexes) + existing = dict((x.definition().Encode(), x) for x in existing_indexes) + + created = 0 + for key, index in requested.iteritems(): + if key not in existing: + datastore_admin.CreateIndex(index) + created += 1 + + deleted = 0 + for key, index in existing.iteritems(): + if key not in requested: + datastore_admin.DeleteIndex(index) + deleted += 1 + + if created or deleted: + logging.info("Created %d and deleted %d index(es); total %d", + created, deleted, len(requested)) diff --git a/google_appengine/google/appengine/tools/dev_appserver_index.pyc b/google_appengine/google/appengine/tools/dev_appserver_index.pyc new file mode 100644 index 0000000000000000000000000000000000000000..653d1dfee0c324caf4cee438b7dc5a13887fd0c3 GIT binary patch literal 8605 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 VTv`>FwCXOp&33cCa%b)Le+O6bdISIf literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_login.py b/google_appengine/google/appengine/tools/dev_appserver_login.py new file mode 100755 index 0000000..e03e9a3 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_login.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Helper CGI for logins/logout in the development application server. + +This CGI has these parameters: + + continue: URL to redirect to after a login or logout has completed. + email: Email address to set for the client. + admin: If 'True', the client should be logged in as an admin. + action: What action to take ('Login' or 'Logout'). + +To view the current user information and a form for logging in and out, +supply no parameters. +""" + + +import cgi +import Cookie +import md5 +import os +import sys +import urllib + + +CONTINUE_PARAM = 'continue' +EMAIL_PARAM = 'email' +ADMIN_PARAM = 'admin' +ACTION_PARAM = 'action' + +LOGOUT_ACTION = 'Logout' +LOGIN_ACTION = 'Login' + +LOGOUT_PARAM = 'action=%s' % LOGOUT_ACTION + +COOKIE_NAME = 'dev_appserver_login' + + +def GetUserInfo(http_cookie, cookie_name=COOKIE_NAME): + """Get the requestor's user info from the HTTP cookie in the CGI environment. + + Args: + http_cookie: Value of the HTTP_COOKIE environment variable. + cookie_name: Name of the cookie that stores the user info. + + Returns: + Tuple (email, admin) where: + email: The user's email address, if any. + admin: True if the user is an admin; False otherwise. + """ + cookie = Cookie.SimpleCookie(http_cookie) + + cookie_value = '' + if cookie_name in cookie: + cookie_value = cookie[cookie_name].value + + email, admin, user_id = (cookie_value.split(':') + ['', '', ''])[:3] + return email, (admin == 'True'), user_id + + +def CreateCookieData(email, admin): + """Creates cookie payload data. + + Args: + email, admin: Parameters to incorporate into the cookie. + + Returns: + String containing the cookie payload. + """ + admin_string = 'False' + if admin: + admin_string = 'True' + if email: + user_id_digest = md5.new(email.lower()).digest() + user_id = '1' + ''.join(['%02d' % ord(x) for x in user_id_digest])[:20] + else: + user_id = '' + return '%s:%s:%s' % (email, admin_string, user_id) + + +def SetUserInfoCookie(email, admin, cookie_name=COOKIE_NAME): + """Creates a cookie to set the user information for the requestor. + + Args: + email: Email to set for the user. + admin: True if the user should be admin; False otherwise. + cookie_name: Name of the cookie that stores the user info. + + Returns: + 'Set-Cookie' header for setting the user info of the requestor. + """ + cookie_value = CreateCookieData(email, admin) + set_cookie = Cookie.SimpleCookie() + set_cookie[cookie_name] = cookie_value + set_cookie[cookie_name]['path'] = '/' + return '%s\r\n' % set_cookie + + +def ClearUserInfoCookie(cookie_name=COOKIE_NAME): + """Clears the user info cookie from the requestor, logging them out. + + Args: + cookie_name: Name of the cookie that stores the user info. + + Returns: + 'Set-Cookie' header for clearing the user info of the requestor. + """ + set_cookie = Cookie.SimpleCookie() + set_cookie[cookie_name] = '' + set_cookie[cookie_name]['path'] = '/' + set_cookie[cookie_name]['max-age'] = '0' + return '%s\r\n' % set_cookie + + +LOGIN_TEMPLATE = """ + + Login + + + +

+
+

%(login_message)s

+

+ + +

+

+ + +

+

+ + +

+
+ +
+ + + +""" + + +def RenderLoginTemplate(login_url, continue_url, email, admin): + """Renders the login page. + + Args: + login_url, continue_url, email, admin: Parameters passed to + LoginCGI. + + Returns: + String containing the contents of the login page. + """ + login_message = 'Not logged in' + if email: + login_message = 'Logged in' + admin_checked = '' + if admin: + admin_checked = 'checked' + + template_dict = { + + + 'email': email or 'test\x40example.com', + 'admin_checked': admin_checked, + 'login_message': login_message, + 'login_url': login_url, + 'continue_url': continue_url + } + + return LOGIN_TEMPLATE % template_dict + + +def LoginRedirect(login_url, + hostname, + port, + relative_url, + outfile): + """Writes a login redirection URL to a user. + + Args: + login_url: Relative URL which should be used for handling user logins. + hostname: Name of the host on which the webserver is running. + port: Port on which the webserver is running. + relative_url: String containing the URL accessed. + outfile: File-like object to which the response should be written. + """ + dest_url = "http://%s:%s%s" % (hostname, port, relative_url) + redirect_url = 'http://%s:%s%s?%s=%s' % (hostname, + port, + login_url, + CONTINUE_PARAM, + urllib.quote(dest_url)) + outfile.write('Status: 302 Requires login\r\n') + outfile.write('Location: %s\r\n\r\n' % redirect_url) + + +def LoginCGI(login_url, + email, + admin, + action, + set_email, + set_admin, + continue_url, + outfile): + """Runs the login CGI. + + This CGI does not care about the method at all. For both POST and GET the + client will be redirected to the continue URL. + + Args: + login_url: URL used to run the CGI. + email: Current email address of the requesting user. + admin: True if the requesting user is an admin; False otherwise. + action: The action used to run the CGI; 'Login' for a login action, 'Logout' + for when a logout should occur. + set_email: Email to set for the user; Empty if no email should be set. + set_admin: True if the user should be an admin; False otherwise. + continue_url: URL to which the user should be redirected when the CGI + finishes loading; defaults to the login_url with no parameters (showing + current status) if not supplied. + outfile: File-like object to which all output data should be written. + """ + redirect_url = '' + output_headers = [] + + if action: + if action.lower() == LOGOUT_ACTION.lower(): + output_headers.append(ClearUserInfoCookie()) + elif set_email: + output_headers.append(SetUserInfoCookie(set_email, set_admin)) + + redirect_url = continue_url or login_url + + if redirect_url: + outfile.write('Status: 302 Redirecting to continue URL\r\n') + for header in output_headers: + outfile.write(header) + outfile.write('Location: %s\r\n' % redirect_url) + outfile.write('\r\n') + else: + outfile.write('Status: 200\r\n') + outfile.write('Content-Type: text/html\r\n') + outfile.write('\r\n') + outfile.write(RenderLoginTemplate(login_url, + continue_url, + email, + admin)) + + +def main(): + """Runs the login and logout CGI script.""" + form = cgi.FieldStorage() + login_url = os.environ['PATH_INFO'] + email = os.environ.get('USER_EMAIL', '') + admin = os.environ.get('USER_IS_ADMIN', '0') == '1' + + action = form.getfirst(ACTION_PARAM) + set_email = form.getfirst(EMAIL_PARAM, '') + set_admin = form.getfirst(ADMIN_PARAM, '') == 'True' + continue_url = form.getfirst(CONTINUE_PARAM, '') + + LoginCGI(login_url, + email, + admin, + action, + set_email, + set_admin, + continue_url, + sys.stdout) + return 0 + + +if __name__ == '__main__': + main() diff --git a/google_appengine/google/appengine/tools/dev_appserver_login.pyc b/google_appengine/google/appengine/tools/dev_appserver_login.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c841080bf7a82fb787771a32cb99dd1aec0bf40 GIT binary patch literal 8758 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 P`QFIwkxxf%m8^dQd4c_8 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_main.py b/google_appengine/google/appengine/tools/dev_appserver_main.py new file mode 100755 index 0000000..01d76c0 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_main.py @@ -0,0 +1,449 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Runs a development application server for an application. + +%(script)s [options] + +Application root must be the path to the application to run in this server. +Must contain a valid app.yaml or app.yml file. + +Options: + --help, -h View this helpful message. + --debug, -d Use debug logging. (Default false) + --clear_datastore, -c Clear the Datastore on startup. (Default false) + --address=ADDRESS, -a ADDRESS + Address to which this server should bind. (Default + %(address)s). + --port=PORT, -p PORT Port for the server to run on. (Default %(port)s) + --blobstore_path=PATH Path to use for storing Blobstore file stub data. + --datastore_path=PATH Path to use for storing Datastore file stub data. + (Default %(datastore_path)s) + --use_sqlite Use the new, SQLite based datastore stub. + (Default false) + --history_path=PATH Path to use for storing Datastore history. + (Default %(history_path)s) + --require_indexes Disallows queries that require composite indexes + not defined in index.yaml. + --smtp_host=HOSTNAME SMTP host to send test mail to. Leaving this + unset will disable SMTP mail sending. + (Default '%(smtp_host)s') + --smtp_port=PORT SMTP port to send test mail to. + (Default %(smtp_port)s) + --smtp_user=USER SMTP user to connect as. Stub will only attempt + to login if this field is non-empty. + (Default '%(smtp_user)s'). + --smtp_password=PASSWORD Password for SMTP server. + (Default '%(smtp_password)s') + --enable_sendmail Enable sendmail when SMTP not configured. + (Default false) + --show_mail_body Log the body of emails in mail stub. + (Default false) + --auth_domain Authorization domain that this app runs in. + (Default gmail.com) + --debug_imports Enables debug logging for module imports, showing + search paths used for finding modules and any + errors encountered during the import process. + --allow_skipped_files Allow access to files matched by app.yaml's + skipped_files (default False) + --disable_static_caching Never allow the browser to cache static files. + (Default enable if expiration set in app.yaml) + --disable_task_running When supplied, tasks will not be automatically + run after submission and must be run manually + in the local admin console. + --task_retry_seconds How long to wait in seconds before retrying a + task after it fails during execution. + (Default '%(task_retry_seconds)s') +""" + + + +from google.appengine.tools import os_compat + +import getopt +import logging +import os +import signal +import sys +import traceback +import tempfile + +logging.basicConfig( + level=logging.INFO, + format='%(levelname)-8s %(asctime)s %(filename)s:%(lineno)s] %(message)s') + +from google.appengine.api import yaml_errors +from google.appengine.dist import py_zipimport +from google.appengine.tools import appcfg +from google.appengine.tools import appengine_rpc +from google.appengine.tools import dev_appserver + + + +DEFAULT_ADMIN_CONSOLE_SERVER = 'appengine.google.com' + +ARG_ADDRESS = 'address' +ARG_ADMIN_CONSOLE_SERVER = 'admin_console_server' +ARG_ADMIN_CONSOLE_HOST = 'admin_console_host' +ARG_AUTH_DOMAIN = 'auth_domain' +ARG_CLEAR_DATASTORE = 'clear_datastore' +ARG_BLOBSTORE_PATH = 'blobstore_path' +ARG_DATASTORE_PATH = 'datastore_path' +ARG_USE_SQLITE = 'use_sqlite' +ARG_DEBUG_IMPORTS = 'debug_imports' +ARG_ENABLE_SENDMAIL = 'enable_sendmail' +ARG_SHOW_MAIL_BODY = 'show_mail_body' +ARG_HISTORY_PATH = 'history_path' +ARG_LOGIN_URL = 'login_url' +ARG_LOG_LEVEL = 'log_level' +ARG_PORT = 'port' +ARG_REQUIRE_INDEXES = 'require_indexes' +ARG_ALLOW_SKIPPED_FILES = 'allow_skipped_files' +ARG_SMTP_HOST = 'smtp_host' +ARG_SMTP_PASSWORD = 'smtp_password' +ARG_SMTP_PORT = 'smtp_port' +ARG_SMTP_USER = 'smtp_user' +ARG_STATIC_CACHING = 'static_caching' +ARG_TEMPLATE_DIR = 'template_dir' +ARG_DISABLE_TASK_RUNNING = 'disable_task_running' +ARG_TASK_RETRY_SECONDS = 'task_retry_seconds' +ARG_TRUSTED = 'trusted' + +SDK_PATH = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.dirname(os_compat.__file__) + ) + ) + ) + +DEFAULT_ARGS = { + ARG_PORT: 8080, + ARG_LOG_LEVEL: logging.INFO, + ARG_BLOBSTORE_PATH: os.path.join(tempfile.gettempdir(), + 'dev_appserver.blobstore'), + ARG_DATASTORE_PATH: os.path.join(tempfile.gettempdir(), + 'dev_appserver.datastore'), + ARG_USE_SQLITE: False, + ARG_HISTORY_PATH: os.path.join(tempfile.gettempdir(), + 'dev_appserver.datastore.history'), + ARG_LOGIN_URL: '/_ah/login', + ARG_CLEAR_DATASTORE: False, + ARG_REQUIRE_INDEXES: False, + ARG_TEMPLATE_DIR: os.path.join(SDK_PATH, 'templates'), + ARG_SMTP_HOST: '', + ARG_SMTP_PORT: 25, + ARG_SMTP_USER: '', + ARG_SMTP_PASSWORD: '', + ARG_ENABLE_SENDMAIL: False, + ARG_SHOW_MAIL_BODY: False, + ARG_AUTH_DOMAIN: 'gmail.com', + ARG_ADDRESS: 'localhost', + ARG_ADMIN_CONSOLE_SERVER: DEFAULT_ADMIN_CONSOLE_SERVER, + ARG_ADMIN_CONSOLE_HOST: None, + ARG_ALLOW_SKIPPED_FILES: False, + ARG_STATIC_CACHING: True, + ARG_DISABLE_TASK_RUNNING: False, + ARG_TASK_RETRY_SECONDS: 30, + ARG_TRUSTED: False, +} + + +def PrintUsageExit(code): + """Prints usage information and exits with a status code. + + Args: + code: Status code to pass to sys.exit() after displaying usage information. + """ + render_dict = DEFAULT_ARGS.copy() + render_dict['script'] = os.path.basename(sys.argv[0]) + print sys.modules['__main__'].__doc__ % render_dict + sys.stdout.flush() + sys.exit(code) + + +def ParseArguments(argv): + """Parses command-line arguments. + + Args: + argv: Command-line arguments, including the executable name, used to + execute this application. + + Returns: + Tuple (args, option_dict) where: + args: List of command-line arguments following the executable name. + option_dict: Dictionary of parsed flags that maps keys from DEFAULT_ARGS + to their values, which are either pulled from the defaults, or from + command-line flags. + """ + option_dict = DEFAULT_ARGS.copy() + + try: + opts, args = getopt.gnu_getopt( + argv[1:], + 'a:cdhp:', + [ 'address=', + 'admin_console_server=', + 'admin_console_host=', + 'allow_skipped_files', + 'auth_domain=', + 'clear_datastore', + 'blobstore_path=', + 'datastore_path=', + 'use_sqlite', + 'debug', + 'debug_imports', + 'enable_sendmail', + 'disable_static_caching', + 'show_mail_body', + 'help', + 'history_path=', + 'port=', + 'require_indexes', + 'smtp_host=', + 'smtp_password=', + 'smtp_port=', + 'smtp_user=', + 'disable_task_running', + 'task_retry_seconds=', + 'template_dir=', + 'trusted', + ]) + except getopt.GetoptError, e: + print >>sys.stderr, 'Error: %s' % e + PrintUsageExit(1) + + for option, value in opts: + if option in ('-h', '--help'): + PrintUsageExit(0) + + if option in ('-d', '--debug'): + option_dict[ARG_LOG_LEVEL] = logging.DEBUG + + if option in ('-p', '--port'): + try: + option_dict[ARG_PORT] = int(value) + if not (65535 > option_dict[ARG_PORT] > 0): + raise ValueError + except ValueError: + print >>sys.stderr, 'Invalid value supplied for port' + PrintUsageExit(1) + + if option in ('-a', '--address'): + option_dict[ARG_ADDRESS] = value + + if option == '--blobstore_path': + option_dict[ARG_BLOBSTORE_PATH] = os.path.abspath(value) + + if option == '--datastore_path': + option_dict[ARG_DATASTORE_PATH] = os.path.abspath(value) + + if option == '--use_sqlite': + option_dict[ARG_USE_SQLITE] = True + + if option == '--history_path': + option_dict[ARG_HISTORY_PATH] = os.path.abspath(value) + + if option in ('-c', '--clear_datastore'): + option_dict[ARG_CLEAR_DATASTORE] = True + + if option == '--require_indexes': + option_dict[ARG_REQUIRE_INDEXES] = True + + if option == '--smtp_host': + option_dict[ARG_SMTP_HOST] = value + + if option == '--smtp_port': + try: + option_dict[ARG_SMTP_PORT] = int(value) + if not (65535 > option_dict[ARG_SMTP_PORT] > 0): + raise ValueError + except ValueError: + print >>sys.stderr, 'Invalid value supplied for SMTP port' + PrintUsageExit(1) + + if option == '--smtp_user': + option_dict[ARG_SMTP_USER] = value + + if option == '--smtp_password': + option_dict[ARG_SMTP_PASSWORD] = value + + if option == '--enable_sendmail': + option_dict[ARG_ENABLE_SENDMAIL] = True + + if option == '--show_mail_body': + option_dict[ARG_SHOW_MAIL_BODY] = True + + if option == '--auth_domain': + option_dict['_DEFAULT_ENV_AUTH_DOMAIN'] = value + + if option == '--debug_imports': + option_dict['_ENABLE_LOGGING'] = True + + if option == '--template_dir': + option_dict[ARG_TEMPLATE_DIR] = value + + if option == '--admin_console_server': + option_dict[ARG_ADMIN_CONSOLE_SERVER] = value.strip() + + if option == '--admin_console_host': + option_dict[ARG_ADMIN_CONSOLE_HOST] = value + + if option == '--allow_skipped_files': + option_dict[ARG_ALLOW_SKIPPED_FILES] = True + + if option == '--disable_static_caching': + option_dict[ARG_STATIC_CACHING] = False + + if option == '--disable_task_running': + option_dict[ARG_DISABLE_TASK_RUNNING] = True + + if option == '--task_retry_seconds': + try: + option_dict[ARG_TASK_RETRY_SECONDS] = int(value) + if option_dict[ARG_TASK_RETRY_SECONDS] < 0: + raise ValueError + except ValueError: + print >>sys.stderr, 'Invalid value supplied for task_retry_seconds' + PrintUsageExit(1) + + if option == '--trusted': + option_dict[ARG_TRUSTED] = True + + return args, option_dict + + +def MakeRpcServer(option_dict): + """Create a new HttpRpcServer. + + Creates a new HttpRpcServer to check for updates to the SDK. + + Args: + option_dict: The dict of command line options. + + Returns: + A HttpRpcServer. + """ + server = appengine_rpc.HttpRpcServer( + option_dict[ARG_ADMIN_CONSOLE_SERVER], + lambda: ('unused_email', 'unused_password'), + appcfg.GetUserAgent(), + appcfg.GetSourceName(), + host_override=option_dict[ARG_ADMIN_CONSOLE_HOST]) + server.authenticated = True + return server + + +def SigTermHandler(signum, frame): + """Handler for TERM signal. + + Raises a KeyboardInterrupt to perform a graceful shutdown on SIGTERM signal. + """ + raise KeyboardInterrupt() + + +def main(argv): + """Runs the development application server.""" + args, option_dict = ParseArguments(argv) + + if len(args) != 1: + print >>sys.stderr, 'Invalid arguments' + PrintUsageExit(1) + + root_path = args[0] + + if '_DEFAULT_ENV_AUTH_DOMAIN' in option_dict: + auth_domain = option_dict['_DEFAULT_ENV_AUTH_DOMAIN'] + dev_appserver.DEFAULT_ENV['AUTH_DOMAIN'] = auth_domain + if '_ENABLE_LOGGING' in option_dict: + enable_logging = option_dict['_ENABLE_LOGGING'] + dev_appserver.HardenedModulesHook.ENABLE_LOGGING = enable_logging + + log_level = option_dict[ARG_LOG_LEVEL] + port = option_dict[ARG_PORT] + blobstore_path = option_dict[ARG_BLOBSTORE_PATH] + datastore_path = option_dict[ARG_DATASTORE_PATH] + login_url = option_dict[ARG_LOGIN_URL] + template_dir = option_dict[ARG_TEMPLATE_DIR] + serve_address = option_dict[ARG_ADDRESS] + require_indexes = option_dict[ARG_REQUIRE_INDEXES] + allow_skipped_files = option_dict[ARG_ALLOW_SKIPPED_FILES] + static_caching = option_dict[ARG_STATIC_CACHING] + + option_dict['root_path'] = os.path.realpath(root_path) + + logging.getLogger().setLevel(log_level) + + config = None + try: + config, matcher = dev_appserver.LoadAppConfig(root_path, {}) + except yaml_errors.EventListenerError, e: + logging.error('Fatal error when loading application configuration:\n' + + str(e)) + return 1 + except dev_appserver.InvalidAppConfigError, e: + logging.error('Application configuration file invalid:\n%s', e) + return 1 + + if option_dict[ARG_ADMIN_CONSOLE_SERVER] != '': + server = MakeRpcServer(option_dict) + update_check = appcfg.UpdateCheck(server, config) + update_check.CheckSupportedVersion() + if update_check.AllowedToCheckForUpdates(): + update_check.CheckForUpdates() + + try: + dev_appserver.SetupStubs(config.application, **option_dict) + except: + exc_type, exc_value, exc_traceback = sys.exc_info() + logging.error(str(exc_type) + ': ' + str(exc_value)) + logging.debug(''.join(traceback.format_exception( + exc_type, exc_value, exc_traceback))) + return 1 + + http_server = dev_appserver.CreateServer( + root_path, + login_url, + port, + template_dir, + sdk_dir=SDK_PATH, + serve_address=serve_address, + require_indexes=require_indexes, + allow_skipped_files=allow_skipped_files, + static_caching=static_caching) + + signal.signal(signal.SIGTERM, SigTermHandler) + + logging.info('Running application %s on port %d: http://%s:%d', + config.application, port, serve_address, port) + try: + try: + http_server.serve_forever() + except KeyboardInterrupt: + logging.info('Server interrupted by user, terminating') + except: + exc_info = sys.exc_info() + info_string = '\n'.join(traceback.format_exception(*exc_info)) + logging.error('Error encountered:\n%s\nNow terminating.', info_string) + return 1 + finally: + http_server.server_close() + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/google_appengine/google/appengine/tools/dev_appserver_oauth.py b/google_appengine/google/appengine/tools/dev_appserver_oauth.py new file mode 100755 index 0000000..5610bc8 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_oauth.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Helper CGI for OAuth in the development app server.""" + + + +import cgi + + +_GET_REQUEST_TOKEN_URL = '/_ah/OAuthGetRequestToken' +_AUTHORIZE_TOKEN_URL = '/_ah/OAuthAuthorizeToken' +_GET_ACCESS_TOKEN_URL = '/_ah/OAuthGetAccessToken' + +_OAUTH_CALLBACK_PARAM = 'oauth_callback' + +OAUTH_URL_PATTERN = (_GET_REQUEST_TOKEN_URL + + '|' + _AUTHORIZE_TOKEN_URL + + '|' + _GET_ACCESS_TOKEN_URL) + + +TOKEN_APPROVAL_TEMPLATE = """ + + OAuth Access Request + + + +
+
+

OAuth Access Request

+ +

+ +

+
+
+ + + +""" + +TOKEN_APPROVED_TEMPLATE = """ + + OAuth Access Granted + + + +
+

OAuth Access Granted

+
+ + + +""" + + +def RenderTokenApprovalTemplate(oauth_callback): + """Renders the token approval page. + + Args: + oauth_callback: Parameter passed to OAuthAuthorizeTokenCGI. + + Returns: + String containing the contents of the token approval page. + """ + template_dict = { + 'oauth_callback': cgi.escape(oauth_callback, quote=True), + } + + return TOKEN_APPROVAL_TEMPLATE % template_dict + + +def RenderTokenApprovedTemplate(): + """Renders the token approved page. + + Returns: + String containing the contents of the token approved page. + """ + return TOKEN_APPROVED_TEMPLATE + + +def OAuthGetRequestTokenCGI(outfile): + """Runs the OAuthGetRequestToken CGI. + + Args: + outfile: File-like object to which all output data should be written. + """ + outfile.write('Status: 200\r\n') + outfile.write('Content-Type: text/plain\r\n') + outfile.write('\r\n') + outfile.write('oauth_token=REQUEST_TOKEN') + outfile.write('&') + outfile.write('oauth_token_secret=REQUEST_TOKEN_SECRET') + + +def OAuthAuthorizeTokenCGI(method, parameters, outfile): + """Runs the OAuthAuthorizeToken CGI. + + Args: + method: HTTP method + parameters: Dictionary of parameters from the request. + outfile: File-like object to which all output data should be written. + """ + oauth_callback = GetFirst(parameters, _OAUTH_CALLBACK_PARAM, '') + if method == 'GET': + outfile.write('Status: 200\r\n') + outfile.write('Content-Type: text/html\r\n') + outfile.write('\r\n') + outfile.write(RenderTokenApprovalTemplate(oauth_callback)) + elif method == 'POST': + if oauth_callback: + outfile.write('Status: 302 Redirecting to callback URL\r\n') + outfile.write('Location: %s\r\n' % oauth_callback) + outfile.write('\r\n') + else: + outfile.write('Status: 200\r\n') + outfile.write('Content-Type: text/html\r\n') + outfile.write('\r\n') + outfile.write(RenderTokenApprovedTemplate()) + else: + outfile.write('Status: 400 Unsupported method\r\n') + + +def OAuthGetAccessTokenCGI(outfile): + """Runs the OAuthGetAccessToken CGI. + + Args: + outfile: File-like object to which all output data should be written. + """ + outfile.write('Status: 200\r\n') + outfile.write('Content-Type: text/plain\r\n') + outfile.write('\r\n') + outfile.write('oauth_token=ACCESS_TOKEN') + outfile.write('&') + outfile.write('oauth_token_secret=ACCESS_TOKEN_SECRET') + + +def GetFirst(parameters, key, default=None): + """Returns the first value of the given key. + + Args: + parameters: A dictionary of lists, {key: [value1, value2]} + key: name of parameter to retrieve + default: value to return if the key isn't found + + Returns: + The first value in the list, or default. + """ + if key in parameters: + if parameters[key]: + return parameters[key][0] + return default + + +def MainCGI(method, path, unused_headers, parameters, outfile): + """CGI for all OAuth handlers. + + Args: + method: HTTP method + path: Path of the request + unused_headers: Instance of mimetools.Message with headers from the request. + parameters: Dictionary of parameters from the request. + outfile: File-like object to which all output data should be written. + """ + if method != 'GET' and method != 'POST': + outfile.write('Status: 400\r\n') + return + + if path == _GET_REQUEST_TOKEN_URL: + OAuthGetRequestTokenCGI(outfile) + elif path == _AUTHORIZE_TOKEN_URL: + OAuthAuthorizeTokenCGI(method, parameters, outfile) + elif path == _GET_ACCESS_TOKEN_URL: + OAuthGetAccessTokenCGI(outfile) + else: + outfile.write('Status: 404 Unknown OAuth handler\r\n') + + +def CreateOAuthDispatcher(): + """Function to create OAuth dispatcher. + + Returns: + New dispatcher capable of handling requests to the built-in OAuth handlers. + """ + + from google.appengine.tools import dev_appserver + + class OAuthDispatcher(dev_appserver.URLDispatcher): + """Dispatcher that handles requests to the built-in OAuth handlers.""" + + def Dispatch(self, + request, + outfile, + base_env_dict=None): + """Handles dispatch to OAuth handlers. + + Args: + request: AppServerRequest. + outfile: The response file. + base_env_dict: Dictionary of CGI environment parameters if available. + Defaults to None. + """ + if not base_env_dict: + outfile.write('Status: 500\r\n') + return + method, path, headers, parameters = self._Parse(request, base_env_dict) + MainCGI(method, path, headers, parameters, outfile) + + def _Parse(self, request, base_env_dict): + """Parses a request into convenient pieces. + + Args: + request: AppServerRequest. + base_env_dict: Dictionary of CGI environment parameters. + + Returns: + A tuple (method, path, headers, parameters) of the HTTP method, the + path (minus query string), an instance of mimetools.Message with + headers from the request, and a dictionary of parameter lists from the + body or query string (in the form of {key :[value1, value2]}). + """ + method = base_env_dict['REQUEST_METHOD'] + path, query = dev_appserver.SplitURL(request.relative_url) + parameters = {} + if method == 'POST': + form = cgi.FieldStorage(fp=request.infile, + headers=request.headers, + environ=base_env_dict) + for key in form: + if key not in parameters: + parameters[key] = [] + for value in form.getlist(key): + parameters[key].append(value) + elif method == 'GET': + parameters = cgi.parse_qs(query) + return method, path, request.headers, parameters + + return OAuthDispatcher() diff --git a/google_appengine/google/appengine/tools/dev_appserver_oauth.pyc b/google_appengine/google/appengine/tools/dev_appserver_oauth.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89fff28514b0e607e964abd4fd9713c42d2cc144 GIT binary patch literal 8275 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/dev_appserver_upload.py b/google_appengine/google/appengine/tools/dev_appserver_upload.py new file mode 100755 index 0000000..958c262 --- /dev/null +++ b/google_appengine/google/appengine/tools/dev_appserver_upload.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Helper CGI for POST uploads. + +Utility library contains the main logic behind simulating the blobstore +uploading mechanism. + +Contents: + GenerateBlobKey: Function for generation unique blob-keys. + UploadCGIHandler: Main CGI handler class for post uploads. +""" + + +import base64 +import cStringIO +import datetime +import md5 +import random +import time + +from google.appengine.api import datastore +from google.appengine.api import datastore_errors +from google.appengine.api.blobstore import blobstore + + +try: + from email.mime import base + from email.mime import multipart + from email import generator +except ImportError: + from email import Generator as generator + from email import MIMEBase as base + from email import MIMEMultipart as multipart + +STRIPPED_HEADERS = frozenset(('content-length', + 'content-md5', + 'content-type', + )) + + +class Error(Exception): + """Base class for upload processing errors.""" + + +class InvalidMIMETypeFormatError(Error): + """MIME type was formatted incorrectly.""" + + +def GenerateBlobKey(time_func=time.time, random_func=random.random): + """Generate a unique BlobKey. + + BlobKey is generated using the current time stamp combined with a random + number. The two values are subject to an md5 digest and base64 url-safe + encoded. The new key is checked against the possibility of existence within + the datastore and the random number is regenerated until there is no match. + + Args: + time_func: Function used for generating the timestamp. Used for + dependency injection. Allows for predictable results during tests. + Must return a floating point UTC timestamp. + random_func: Function used for generating the random number. Used for + dependency injection. Allows for predictable results during tests. + + Returns: + String version of BlobKey that is unique within the BlobInfo datastore. + None if there are too many name conflicts. + """ + timestamp = str(time_func()) + tries = 0 + while tries < 10: + number = str(random_func()) + digester = md5.md5() + digester.update(timestamp) + digester.update(number) + blob_key = base64.urlsafe_b64encode(digester.digest()) + datastore_key = datastore.Key.from_path(blobstore.BLOB_INFO_KIND, + blob_key, + namespace='') + try: + datastore.Get(datastore_key) + tries += 1 + except datastore_errors.EntityNotFoundError: + return blob_key + return None + + +def _SplitMIMEType(mime_type): + """Split MIME-type in to main and sub type. + + Args: + mime_type: full MIME type string. + + Returns: + (main, sub): + main: Main part of mime type (application, image, text, etc). + sub: Subtype part of mime type (pdf, png, html, etc). + + Raises: + InvalidMIMETypeFormatError: If form item has incorrectly formatted MIME + type. + """ + if mime_type: + mime_type_array = mime_type.split('/') + + if len(mime_type_array) == 1: + raise InvalidMIMETypeFormatError('Missing MIME sub-type.') + elif len(mime_type_array) == 2: + main_type, sub_type = mime_type_array + if not(main_type and sub_type): + raise InvalidMIMETypeFormatError( + 'Incorrectly formatted MIME type: %s' % mime_type) + return main_type, sub_type + else: + raise InvalidMIMETypeFormatError( + 'Incorrectly formatted MIME type: %s' % mime_type) + else: + return 'application', 'octet-stream' + + +class UploadCGIHandler(object): + """Class used for handling an upload post. + + The main interface to this class is the UploadCGI method. This will recieve + the upload form, store the blobs contained in the post and rewrite the blobs + to contain BlobKeys instead of blobs. + """ + + def __init__(self, + blob_storage, + generate_blob_key=GenerateBlobKey, + now_func=datetime.datetime.now): + """Constructor. + + Args: + blob_storage: BlobStorage instance where actual blobs are stored. + generate_blob_key: Function used for generating unique blob keys. + now_func: Function that returns the current timestamp. + """ + self.__blob_storage = blob_storage + self.__generate_blob_key = generate_blob_key + self.__now_func = now_func + + def StoreBlob(self, form_item, creation): + """Store form-item to blob storage. + + Args: + form_item: FieldStorage instance that represents a specific form field. + This instance should have a non-empty filename attribute, meaning that + it is an uploaded blob rather than a normal form field. + creation: Timestamp to associate with new blobs creation time. This + parameter is provided so that all blobs in the same upload form can have + the same creation date. + + Returns: + datastore.Entity('__BlobInfo__') associated with the upload. + """ + main_type, sub_type = _SplitMIMEType(form_item.type) + + blob_key = self.__generate_blob_key() + self.__blob_storage.StoreBlob(blob_key, form_item.file) + content_type_formatter = base.MIMEBase(main_type, sub_type, + **form_item.type_options) + + blob_entity = datastore.Entity('__BlobInfo__', + name=str(blob_key), + namespace='') + blob_entity['content_type'] = ( + content_type_formatter['content-type'].decode('utf-8')) + blob_entity['creation'] = creation + blob_entity['filename'] = form_item.filename.decode('utf-8') + form_item.file.seek(0, 2) + size = form_item.file.tell() + form_item.file.seek(0) + blob_entity['size'] = size + datastore.Put(blob_entity) + return blob_entity + + def _GenerateMIMEMessage(self, form, boundary=None): + """Generate a new post from original form. + + Also responsible for storing blobs in the datastore. + + Args: + form: Instance of cgi.FieldStorage representing the whole form + derived from original post data. + boundary: Boundary to use for resulting form. Used only in tests so + that the boundary is always consistent. + + Returns: + A MIMEMultipart instance representing the new HTTP post which should be + forwarded to the developers actual CGI handler. DO NOT use the return + value of this method to generate a string unless you know what you're + doing and properly handle folding whitespace (from rfc822) properly. + """ + message = multipart.MIMEMultipart('form-data', boundary) + for name, value in form.headers.items(): + if name.lower() not in STRIPPED_HEADERS: + message.add_header(name, value) + + def IterateForm(): + """Flattens form in to single sequence of cgi.FieldStorage instances. + + The resulting cgi.FieldStorage objects are a little bit irregular in + their structure. A single name can have mulitple sub-items. In this + case, the root FieldStorage object has a list associated with that field + name. Otherwise, the root FieldStorage object just refers to a single + nested instance. + + Lists of FieldStorage instances occur when a form has multiple values + for the same name. + + Yields: + cgi.FieldStorage irrespective of their nesting level. + """ + for key in sorted(form): + form_item = form[key] + if isinstance(form_item, list): + for list_item in form_item: + yield list_item + else: + yield form_item + + creation = self.__now_func() + for form_item in IterateForm(): + + disposition_parameters = {'name': form_item.name} + + if form_item.filename is None: + variable = base.MIMEBase('text', 'plain') + variable.set_payload(form_item.value) + else: + if not form_item.filename: + continue + + disposition_parameters['filename'] = form_item.filename + + main_type, sub_type = _SplitMIMEType(form_item.type) + + blob_entity = self.StoreBlob(form_item, creation) + + variable = base.MIMEBase('message', + 'external-body', + access_type=blobstore.BLOB_KEY_HEADER, + blob_key=blob_entity.key().name()) + + form_item.file.seek(0, 2) + content_length = form_item.file.tell() + form_item.file.seek(0) + + external = base.MIMEBase(main_type, + sub_type, + **form_item.type_options) + headers = dict(form_item.headers) + headers['Content-Length'] = str(content_length) + headers[blobstore.UPLOAD_INFO_CREATION_HEADER] = ( + blobstore._format_creation(creation)) + for key, value in headers.iteritems(): + external.add_header(key, value) + + external_disposition_parameters = dict(disposition_parameters) + external_disposition_parameters['filename'] = form_item.filename + if not external.get('Content-Disposition'): + external.add_header('Content-Disposition', + 'form-data', + **external_disposition_parameters) + variable.set_payload([external]) + + variable.add_header('Content-Disposition', + 'form-data', + **disposition_parameters) + message.attach(variable) + + return message + + def GenerateMIMEMessageString(self, form, boundary=None): + """Generate a new post string from original form. + + Args: + form: Instance of cgi.FieldStorage representing the whole form + derived from original post data. + boundary: Boundary to use for resulting form. Used only in tests so + that the boundary is always consistent. + + Returns: + A string rendering of a MIMEMultipart instance. + """ + message = self._GenerateMIMEMessage(form, boundary=boundary) + message_out = cStringIO.StringIO() + gen = generator.Generator(message_out, maxheaderlen=0) + gen.flatten(message, unixfrom=False) + return message_out.getvalue() diff --git a/google_appengine/google/appengine/tools/dev_appserver_upload.pyc b/google_appengine/google/appengine/tools/dev_appserver_upload.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2344506ed37e3eaf126a6ab88b4af56ac917de0e GIT binary patch literal 10782 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/os_compat.py b/google_appengine/google/appengine/tools/os_compat.py new file mode 100755 index 0000000..4bfa34f --- /dev/null +++ b/google_appengine/google/appengine/tools/os_compat.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""OS cross-platform compatibility tweaks. + +This module will, on import, change some parts of the running evironment so +that other modules do not need special handling when running on different +operating systems, such as Linux/Mac OSX/Windows. + +Some of these changes must be done before other modules are imported, so +always import this module first. +""" + + +import os +os.environ['TZ'] = 'UTC' +import time +if hasattr(time, 'tzset'): + time.tzset() + +import __builtin__ + + +if 'WindowsError' in __builtin__.__dict__: + WindowsError = WindowsError +else: + class WindowsError(Exception): + """A fake Windows Error exception which should never be thrown.""" + + +ERROR_PATH_NOT_FOUND = 3 +ERROR_ACCESS_DENIED = 5 +ERROR_ALREADY_EXISTS = 183 \ No newline at end of file diff --git a/google_appengine/google/appengine/tools/os_compat.pyc b/google_appengine/google/appengine/tools/os_compat.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59df18cf72907e264fdad25a12cc7e5d57f92aff GIT binary patch literal 1184 zcwV(rU2fAr5FRJNZQZo01mZ{HVIT7lk_%Lb&;(Ib5@e^MwUDf>cM~tQcP;OvX?cZP za1f4xxC$45S;w@9cP#C!=Vw2E^X>S@&%NOLQ*n|)^Z2x0(6V322!NQZ2ciX+9>nz0 z2I+xnf#|?tlW0Rs(GJKqhVU zM^r_b9avE97JLDB_?$9=@&Sk)h<8B*5O*C^)Z29=KX?GMh^3XW_F0*8T$rk0sVPd1 z>O$oTZy2s6zqErOm}kne!iZHa*;?g!pBc?mQJM<-EX}xHN@h(VS;;H3%q$pYl2t3M zm0mJ=r7EKfsga5T%s4Vb`Km#*OcRTxKtWb-EtOzVH zrL2f7h3$<+S=c_at2ATWvJ<6O*Tc6wWwYpP_+DvY)~=Drk=)R;vSCi0tt_$&Nj$Wq zos^LOFys{3^eaW*b;0vBzp+h>;O_KUsLJ9XP`~JzVwQH?&ViLOnWUTM$M)m$QdtMr^?^C2Ig!jlkq}Doa$P(K=J4gU!q; z7bNQybwcf7R++UP^c*MLCX*zZ7cxmOsGUs%}CrCus6o51icfX3u@Xl$~XvO#$hwi{sU^zl9AeC0$^-&|&oOz8fr zT0diJW>VMlgdG=Gg{*@aw;ZtwT^;V${4Z82r|F=6HoXd!Vv^LK#2qq|KXTy%=Y`=c zOx}&=uaoI)p1hi!PRDrvHZeLnnnY1Do=lG?f7Pn{INaRQ f^Cu4fu~V(x_4d53ci`=}y6pomXg%!sp6~qzXi`4$ literal 0 HcwPel00001 diff --git a/google_appengine/google/appengine/tools/remote_api_shell.py b/google_appengine/google/appengine/tools/remote_api_shell.py new file mode 100755 index 0000000..0984d16 --- /dev/null +++ b/google_appengine/google/appengine/tools/remote_api_shell.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""An interactive python shell that uses remote_api. + +Usage: + remote_api_shell.py [-s HOSTNAME] APPID [PATH] +""" + + +from google.appengine.tools import os_compat + +import atexit +import code +import getpass +import optparse +import os +import sys + +try: + import readline +except ImportError: + readline = None + +from google.appengine.ext.remote_api import remote_api_stub + +from google.appengine.api import datastore +from google.appengine.api import memcache +from google.appengine.api import urlfetch +from google.appengine.api import users +from google.appengine.ext import db +from google.appengine.ext import search + + +HISTORY_PATH = os.path.expanduser('~/.remote_api_shell_history') +DEFAULT_PATH = '/remote_api' +BANNER = """App Engine remote_api shell +Python %s +The db, users, urlfetch, and memcache modules are imported.""" % sys.version + + +def auth_func(): + return (raw_input('Email: '), getpass.getpass('Password: ')) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('-s', '--server', dest='server', + help='The hostname your app is deployed on. ' + 'Defaults to .appspot.com.') + parser.add_option('--secure', dest='secure', action="store_true", + default=False, help='Use HTTPS when communicating ' + 'with the server.') + (options, args) = parser.parse_args() + + if not args or len(args) > 2: + print >> sys.stderr, __doc__ + if len(args) > 2: + print >> sys.stderr, 'Unexpected arguments: %s' % args[2:] + sys.exit(1) + + appid = args[0] + if len(args) == 2: + path = args[1] + else: + path = DEFAULT_PATH + + remote_api_stub.ConfigureRemoteApi(appid, path, auth_func, + servername=options.server, + save_cookies=True, secure=options.secure) + remote_api_stub.MaybeInvokeAuthentication() + + os.environ['SERVER_SOFTWARE'] = 'Development (remote_api_shell)/1.0' + + sys.ps1 = '%s> ' % appid + if readline is not None: + readline.parse_and_bind('tab: complete') + atexit.register(lambda: readline.write_history_file(HISTORY_PATH)) + if os.path.exists(HISTORY_PATH): + readline.read_history_file(HISTORY_PATH) + + code.interact(banner=BANNER, local=globals()) + + +if __name__ == '__main__': + main(sys.argv) diff --git a/google_appengine/google/appengine/tools/requeue.py b/google_appengine/google/appengine/tools/requeue.py new file mode 100755 index 0000000..986bc5c --- /dev/null +++ b/google_appengine/google/appengine/tools/requeue.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""A thread-safe queue in which removed objects put back to the front.""" + + +import logging +import Queue +import threading +import time + +logger = logging.getLogger('google.appengine.tools.requeue') + + +class ReQueue(object): + """A special thread-safe queue. + + A ReQueue allows unfinished work items to be returned with a call to + reput(). When an item is reput, task_done() should *not* be called + in addition, getting an item that has been reput does not increase + the number of outstanding tasks. + + This class shares an interface with Queue.Queue and provides the + additional reput method. + """ + + def __init__(self, + queue_capacity, + requeue_capacity=None, + queue_factory=Queue.Queue, + get_time=time.time): + """Initialize a ReQueue instance. + + Args: + queue_capacity: The number of items that can be put in the ReQueue. + requeue_capacity: The numer of items that can be reput in the ReQueue. + queue_factory: Used for dependency injection. + get_time: Used for dependency injection. + """ + if requeue_capacity is None: + requeue_capacity = queue_capacity + + self.get_time = get_time + self.queue = queue_factory(queue_capacity) + self.requeue = queue_factory(requeue_capacity) + self.lock = threading.Lock() + self.put_cond = threading.Condition(self.lock) + self.get_cond = threading.Condition(self.lock) + + def _DoWithTimeout(self, + action, + exc, + wait_cond, + done_cond, + lock, + timeout=None, + block=True): + """Performs the given action with a timeout. + + The action must be non-blocking, and raise an instance of exc on a + recoverable failure. If the action fails with an instance of exc, + we wait on wait_cond before trying again. Failure after the + timeout is reached is propagated as an exception. Success is + signalled by notifying on done_cond and returning the result of + the action. If action raises any exception besides an instance of + exc, it is immediately propagated. + + Args: + action: A callable that performs a non-blocking action. + exc: An exception type that is thrown by the action to indicate + a recoverable error. + wait_cond: A condition variable which should be waited on when + action throws exc. + done_cond: A condition variable to signal if the action returns. + lock: The lock used by wait_cond and done_cond. + timeout: A non-negative float indicating the maximum time to wait. + block: Whether to block if the action cannot complete immediately. + + Returns: + The result of the action, if it is successful. + + Raises: + ValueError: If the timeout argument is negative. + """ + if timeout is not None and timeout < 0.0: + raise ValueError('\'timeout\' must not be a negative number') + if not block: + timeout = 0.0 + result = None + success = False + start_time = self.get_time() + lock.acquire() + try: + while not success: + try: + result = action() + success = True + except Exception, e: + if not isinstance(e, exc): + raise e + if timeout is not None: + elapsed_time = self.get_time() - start_time + timeout -= elapsed_time + if timeout <= 0.0: + raise e + wait_cond.wait(timeout) + finally: + if success: + done_cond.notify() + lock.release() + return result + + def put(self, item, block=True, timeout=None): + """Put an item into the requeue. + + Args: + item: An item to add to the requeue. + block: Whether to block if the requeue is full. + timeout: Maximum on how long to wait until the queue is non-full. + + Raises: + Queue.Full if the queue is full and the timeout expires. + """ + def PutAction(): + self.queue.put(item, block=False) + self._DoWithTimeout(PutAction, + Queue.Full, + self.get_cond, + self.put_cond, + self.lock, + timeout=timeout, + block=block) + + def reput(self, item, block=True, timeout=None): + """Re-put an item back into the requeue. + + Re-putting an item does not increase the number of outstanding + tasks, so the reput item should be uniquely associated with an + item that was previously removed from the requeue and for which + TaskDone has not been called. + + Args: + item: An item to add to the requeue. + block: Whether to block if the requeue is full. + timeout: Maximum on how long to wait until the queue is non-full. + + Raises: + Queue.Full is the queue is full and the timeout expires. + """ + def ReputAction(): + self.requeue.put(item, block=False) + self._DoWithTimeout(ReputAction, + Queue.Full, + self.get_cond, + self.put_cond, + self.lock, + timeout=timeout, + block=block) + + def get(self, block=True, timeout=None): + """Get an item from the requeue. + + Args: + block: Whether to block if the requeue is empty. + timeout: Maximum on how long to wait until the requeue is non-empty. + + Returns: + An item from the requeue. + + Raises: + Queue.Empty if the queue is empty and the timeout expires. + """ + def GetAction(): + try: + result = self.requeue.get(block=False) + self.requeue.task_done() + except Queue.Empty: + result = self.queue.get(block=False) + return result + return self._DoWithTimeout(GetAction, + Queue.Empty, + self.put_cond, + self.get_cond, + self.lock, + timeout=timeout, + block=block) + + def join(self): + """Blocks until all of the items in the requeue have been processed.""" + self.queue.join() + + def task_done(self): + """Indicate that a previously enqueued item has been fully processed.""" + self.queue.task_done() + + def empty(self): + """Returns true if the requeue is empty.""" + return self.queue.empty() and self.requeue.empty() + + def get_nowait(self): + """Try to get an item from the queue without blocking.""" + return self.get(block=False) + + def qsize(self): + return self.queue.qsize() + self.requeue.qsize() diff --git a/google_appengine/google/appengine/tools/requeue.pyc b/google_appengine/google/appengine/tools/requeue.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0085fffd2561c9c1e575df6e45272067c036adbf GIT binary patch literal 7964 zcwXIEPmdc(6|b^A_SiF%ncY>i5E3ZyXX7Lj53mAO5kkpsvP+~eT6zK#=3@19m+ea0 zT~1dy69@#`d7)EUmByobB<@uduyJ=vUcZHS}w2uf`;2 zYKf&)_6aPhvlN~*SPv#Fv8RlE!r0IDmT~q@yN--LfaZpkzSjLrcH+@U=6#jR zj@3Fdok9{}iz-I)$Jj5`uEF<|i+0+pD46AFCNq+W zinHV9>a4%#>RPylu!mk zTHhUjWJwkq1H!}w5J4`vl|?U3q*ITq(($&*Q!y&^BbCBbU;re7pkP@i%TU^ZPCM`_ zSpw_hw*p^`Rr7ObrWF47mzi!cKIQDN!g3D18k;iK*f4dHM*2chFkJpr9c&#N3wl(>L^kSO}*8uf$=lx`A_ z;zZfWH89+~aeZu{V-heRxDpKkUPK>w>K#wXL+LoJ=aF(&J(-vz1-`Pn07^eG5R*M! zh*Sc8Q<*0dScuRE2Zc2dPhG2q@};J4w_9K`ix}UAptZ}GBRm0%uASwDkRzJ7fG`HJ zMl0{=t2!ejy1fG=CanN20jE&5C_DtYa8qRLan9d%qmGit+BJ_Ei zg~t34nyrBz$}NCOtha9JgFMr5YPMYN>z29CKiHZ*pk%n^d3Ht<61dxiJYl=tOGq7M zVry%>RcZ2d`kdtn&O*7C0}QS}1WxOi^X@m9z5u~rCrmJ=HyF265Fl`W$sIBpYy?n& z35Ruh0P7AR$}8-!Nl{)!JRG*zbP2O9^!bxNu*bh+(ygH{G})dkw76SY%!r6-;Y%ev_EuQ zMBIZ^>=UgB%DHM1Cs>-G3;B2i>nx07%HP0HjtIg+?2MB{LUvb%C>YhxsmO@^3DyhM zBeozBmLw+;*@Oxl)rtXTAT!QD?w*sPRN5KA8-UD+{3kO3Ks!UFV$mc-2F(K54mD82 zp-dHMkWJ>aIUx%>R@b0dW2q#6P^KJ(u!tA@8^rXhfg>!PvyZTok*A$QvoR!0zwvrE&_|vuOwqga^g{3D4OhcWxnq3yLiCu8Sd9)KI7n-qeKSf z4fIPdJ%>Js>s&0?K}RkLxPzF?WstPMVX=e;f^NJ!%-%!oR1P88j;f~M91Cv zP@SW+iG*FG83%{nI13~wY6FFz#@SfjLWf=p@D7-Zi$0X-oTvoNoj!f${|pwGi+;gH zmvK;oecWS|*7Dni?e+@HjyM)MEU`1zR8~KXGb5>VQEiQrhhqg!#6){G^iw5|OWRM{GP2JV0g zS!1BEs?e}Xt$VeE^&M(r>bAB^6%NOp9oq1iuY!p_#&`=Shuk04f?mh7BF>}4=8gv% zx8&mJs+U5ZpN%mN5Xp`K|3@CwfX;zFfaHN-ci4vWl2sW|DQRGqaCA<I2(JV8 z1>Tf`<^d=lj{pHag)JOD-$J{cgXS4Wvn!59247g*L~#y>QMd8mj_`!rM!!YaP{_mH zGkPP0A5A0ppeYF=rx2EiDRA)(!riZtJhEjxx`2|QjN5d)kG_u5U{sK;NG`NjgrJI$ z3IUShhGdQ22?QLeiwkG}C*XSV~w^Yf-KfjphhkkG8Kk8=e~VLt;$Yn>?Co%zjSD`3{`JPu;q z!pTQUj}6QZH#WUO4;L_rc)+t7Z6Vz57R0y-xtT5lT%nP)7SM|KWO?!9z(PLqm-X-w<+^4GrUyKgAF!MeP+9+=B^#5J905eH)v17t}ii9g!k} z%$$d&-m2HCkaE|*Smmqlt)E>#w|>4^bDuU93cQ>kEi>H*f_+y=XeakQ=F1{D{|>yB m1>L%2v#$F$Z?ve+tJjG<^nK@(AH2zYNvu~_IejYY)&Broj{SH5 literal 0 HcwPel00001 diff --git a/google_appengine/google/net/__init__.py b/google_appengine/google/net/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/net/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/net/__init__.pyc b/google_appengine/google/net/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..035cbf327eeb2f23b59851839607d7e1596313e3 GIT binary patch literal 148 zcwW2siI?k{Oo~r30~9a%knvjB+{28Lh_kcgiKNDhrCwgnRU8Tq-X`gzGkiTV02 z`Q>>z`H3mT`sw-k={c$Ki3J6zdFh#XsZas^ywno?`1s7c%#!$cy@JXT4xkA(x%nxj NIjMFadx}Bk0sxvrB4hvn literal 0 HcwPel00001 diff --git a/google_appengine/google/net/proto/ProtocolBuffer.py b/google_appengine/google/net/proto/ProtocolBuffer.py new file mode 100644 index 0000000..459894b --- /dev/null +++ b/google_appengine/google/net/proto/ProtocolBuffer.py @@ -0,0 +1,532 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import struct +import array +import string +import re +from google.pyglib.gexcept import AbstractMethod +import httplib + +__all__ = ['ProtocolMessage', 'Encoder', 'Decoder', + 'ProtocolBufferDecodeError', + 'ProtocolBufferEncodeError', + 'ProtocolBufferReturnError'] + +URL_RE = re.compile('^(https?)://([^/]+)(/.*)$') + +class ProtocolMessage: + + + def __init__(self, contents=None): + raise AbstractMethod + + def Clear(self): + raise AbstractMethod + + def IsInitialized(self, debug_strs=None): + raise AbstractMethod + + def Encode(self): + try: + return self._CEncode() + except AbstractMethod: + e = Encoder() + self.Output(e) + return e.buffer().tostring() + + def _CEncode(self): + raise AbstractMethod + + def ParseFromString(self, s): + self.Clear() + self.MergeFromString(s) + return + + def MergeFromString(self, s): + try: + self._CMergeFromString(s) + dbg = [] + if not self.IsInitialized(dbg): + raise ProtocolBufferDecodeError, '\n\t'.join(dbg) + except AbstractMethod: + a = array.array('B') + a.fromstring(s) + d = Decoder(a, 0, len(a)) + self.Merge(d) + return + + def _CMergeFromString(self, s): + raise AbstractMethod + + def __getstate__(self): + return self.Encode() + + def __setstate__(self, contents_): + self.__init__(contents=contents_) + + def sendCommand(self, server, url, response, follow_redirects=1, + secure=0, keyfile=None, certfile=None): + data = self.Encode() + if secure: + if keyfile and certfile: + conn = httplib.HTTPSConnection(server, key_file=keyfile, + cert_file=certfile) + else: + conn = httplib.HTTPSConnection(server) + else: + conn = httplib.HTTPConnection(server) + conn.putrequest("POST", url) + conn.putheader("Content-Length", "%d" %len(data)) + conn.endheaders() + conn.send(data) + resp = conn.getresponse() + if follow_redirects > 0 and resp.status == 302: + m = URL_RE.match(resp.getheader('Location')) + if m: + protocol, server, url = m.groups() + return self.sendCommand(server, url, response, + follow_redirects=follow_redirects - 1, + secure=(protocol == 'https'), + keyfile=keyfile, + certfile=certfile) + if resp.status != 200: + raise ProtocolBufferReturnError(resp.status) + if response is not None: + response.ParseFromString(resp.read()) + return response + + def sendSecureCommand(self, server, keyfile, certfile, url, response, + follow_redirects=1): + return self.sendCommand(server, url, response, + follow_redirects=follow_redirects, + secure=1, keyfile=keyfile, certfile=certfile) + + def __str__(self, prefix="", printElemNumber=0): + raise AbstractMethod + + def ToASCII(self): + return self._CToASCII(ProtocolMessage._SYMBOLIC_FULL_ASCII) + + def ToCompactASCII(self): + return self._CToASCII(ProtocolMessage._NUMERIC_ASCII) + + def ToShortASCII(self): + return self._CToASCII(ProtocolMessage._SYMBOLIC_SHORT_ASCII) + + _NUMERIC_ASCII = 0 + _SYMBOLIC_SHORT_ASCII = 1 + _SYMBOLIC_FULL_ASCII = 2 + + def _CToASCII(self, output_format): + raise AbstractMethod + + def ParseASCII(self, ascii_string): + raise AbstractMethod + + def ParseASCIIIgnoreUnknown(self, ascii_string): + raise AbstractMethod + + def Equals(self, other): + raise AbstractMethod + + def __eq__(self, other): + if other.__class__ is self.__class__: + return self.Equals(other) + return NotImplemented + + def __ne__(self, other): + if other.__class__ is self.__class__: + return not self.Equals(other) + return NotImplemented + + + def Output(self, e): + dbg = [] + if not self.IsInitialized(dbg): + raise ProtocolBufferEncodeError, '\n\t'.join(dbg) + self.OutputUnchecked(e) + return + + def OutputUnchecked(self, e): + raise AbstractMethod + + def Parse(self, d): + self.Clear() + self.Merge(d) + return + + def Merge(self, d): + self.TryMerge(d) + dbg = [] + if not self.IsInitialized(dbg): + raise ProtocolBufferDecodeError, '\n\t'.join(dbg) + return + + def TryMerge(self, d): + raise AbstractMethod + + def CopyFrom(self, pb): + if (pb == self): return + self.Clear() + self.MergeFrom(pb) + + def MergeFrom(self, pb): + raise AbstractMethod + + + def lengthVarInt32(self, n): + return self.lengthVarInt64(n) + + def lengthVarInt64(self, n): + if n < 0: + return 10 + result = 0 + while 1: + result += 1 + n >>= 7 + if n == 0: + break + return result + + def lengthString(self, n): + return self.lengthVarInt32(n) + n + + def DebugFormat(self, value): + return "%s" % value + def DebugFormatInt32(self, value): + if (value <= -2000000000 or value >= 2000000000): + return self.DebugFormatFixed32(value) + return "%d" % value + def DebugFormatInt64(self, value): + if (value <= -20000000000000 or value >= 20000000000000): + return self.DebugFormatFixed64(value) + return "%d" % value + def DebugFormatString(self, value): + def escape(c): + o = ord(c) + if o == 10: return r"\n" + if o == 39: return r"\'" + + if o == 34: return r'\"' + if o == 92: return r"\\" + + if o >= 127 or o < 32: return "\\%03o" % o + return c + return '"' + "".join([escape(c) for c in value]) + '"' + def DebugFormatFloat(self, value): + return "%ff" % value + def DebugFormatFixed32(self, value): + if (value < 0): value += (1L<<32) + return "0x%x" % value + def DebugFormatFixed64(self, value): + if (value < 0): value += (1L<<64) + return "0x%x" % value + def DebugFormatBool(self, value): + if value: + return "true" + else: + return "false" + +class Encoder: + + NUMERIC = 0 + DOUBLE = 1 + STRING = 2 + STARTGROUP = 3 + ENDGROUP = 4 + FLOAT = 5 + MAX_TYPE = 6 + + def __init__(self): + self.buf = array.array('B') + return + + def buffer(self): + return self.buf + + def put8(self, v): + if v < 0 or v >= (1<<8): raise ProtocolBufferEncodeError, "u8 too big" + self.buf.append(v & 255) + return + + def put16(self, v): + if v < 0 or v >= (1<<16): raise ProtocolBufferEncodeError, "u16 too big" + self.buf.append((v >> 0) & 255) + self.buf.append((v >> 8) & 255) + return + + def put32(self, v): + if v < 0 or v >= (1L<<32): raise ProtocolBufferEncodeError, "u32 too big" + self.buf.append((v >> 0) & 255) + self.buf.append((v >> 8) & 255) + self.buf.append((v >> 16) & 255) + self.buf.append((v >> 24) & 255) + return + + def put64(self, v): + if v < 0 or v >= (1L<<64): raise ProtocolBufferEncodeError, "u64 too big" + self.buf.append((v >> 0) & 255) + self.buf.append((v >> 8) & 255) + self.buf.append((v >> 16) & 255) + self.buf.append((v >> 24) & 255) + self.buf.append((v >> 32) & 255) + self.buf.append((v >> 40) & 255) + self.buf.append((v >> 48) & 255) + self.buf.append((v >> 56) & 255) + return + + def putVarInt32(self, v): + + buf_append = self.buf.append + if v & 127 == v: + buf_append(v) + return + if v >= 0x80000000 or v < -0x80000000: + raise ProtocolBufferEncodeError, "int32 too big" + if v < 0: + v += 0x10000000000000000 + while True: + bits = v & 127 + v >>= 7 + if v: + bits |= 128 + buf_append(bits) + if not v: + break + return + + def putVarInt64(self, v): + buf_append = self.buf.append + if v >= 0x8000000000000000 or v < -0x8000000000000000: + raise ProtocolBufferEncodeError, "int64 too big" + if v < 0: + v += 0x10000000000000000 + while True: + bits = v & 127 + v >>= 7 + if v: + bits |= 128 + buf_append(bits) + if not v: + break + return + + def putVarUint64(self, v): + buf_append = self.buf.append + if v < 0 or v >= 0x10000000000000000: + raise ProtocolBufferEncodeError, "uint64 too big" + while True: + bits = v & 127 + v >>= 7 + if v: + bits |= 128 + buf_append(bits) + if not v: + break + return + + + def putFloat(self, v): + a = array.array('B') + a.fromstring(struct.pack(" self.limit: raise ProtocolBufferDecodeError, "truncated" + self.idx += n + return + + def skipData(self, tag): + t = tag & 7 + if t == Encoder.NUMERIC: + self.getVarInt64() + elif t == Encoder.DOUBLE: + self.skip(8) + elif t == Encoder.STRING: + n = self.getVarInt32() + self.skip(n) + elif t == Encoder.STARTGROUP: + while 1: + t = self.getVarInt32() + if (t & 7) == Encoder.ENDGROUP: + break + else: + self.skipData(t) + if (t - Encoder.ENDGROUP) != (tag - Encoder.STARTGROUP): + raise ProtocolBufferDecodeError, "corrupted" + elif t == Encoder.ENDGROUP: + raise ProtocolBufferDecodeError, "corrupted" + elif t == Encoder.FLOAT: + self.skip(4) + else: + raise ProtocolBufferDecodeError, "corrupted" + + def get8(self): + if self.idx >= self.limit: raise ProtocolBufferDecodeError, "truncated" + c = self.buf[self.idx] + self.idx += 1 + return c + + def get16(self): + if self.idx + 2 > self.limit: raise ProtocolBufferDecodeError, "truncated" + c = self.buf[self.idx] + d = self.buf[self.idx + 1] + self.idx += 2 + return (d << 8) | c + + def get32(self): + if self.idx + 4 > self.limit: raise ProtocolBufferDecodeError, "truncated" + c = self.buf[self.idx] + d = self.buf[self.idx + 1] + e = self.buf[self.idx + 2] + f = long(self.buf[self.idx + 3]) + self.idx += 4 + return (f << 24) | (e << 16) | (d << 8) | c + + def get64(self): + if self.idx + 8 > self.limit: raise ProtocolBufferDecodeError, "truncated" + c = self.buf[self.idx] + d = self.buf[self.idx + 1] + e = self.buf[self.idx + 2] + f = long(self.buf[self.idx + 3]) + g = long(self.buf[self.idx + 4]) + h = long(self.buf[self.idx + 5]) + i = long(self.buf[self.idx + 6]) + j = long(self.buf[self.idx + 7]) + self.idx += 8 + return ((j << 56) | (i << 48) | (h << 40) | (g << 32) | (f << 24) + | (e << 16) | (d << 8) | c) + + def getVarInt32(self): + b = self.get8() + if not (b & 128): + return b + + result = long(0) + shift = 0 + + while 1: + result |= (long(b & 127) << shift) + shift += 7 + if not (b & 128): + if result >= 0x10000000000000000L: + raise ProtocolBufferDecodeError, "corrupted" + break + if shift >= 64: raise ProtocolBufferDecodeError, "corrupted" + b = self.get8() + + if result >= 0x8000000000000000L: + result -= 0x10000000000000000L + if result >= 0x80000000L or result < -0x80000000L: + raise ProtocolBufferDecodeError, "corrupted" + return result + + def getVarInt64(self): + result = self.getVarUint64() + if result >= (1L << 63): + result -= (1L << 64) + return result + + def getVarUint64(self): + result = long(0) + shift = 0 + while 1: + if shift >= 64: raise ProtocolBufferDecodeError, "corrupted" + b = self.get8() + result |= (long(b & 127) << shift) + shift += 7 + if not (b & 128): + if result >= (1L << 64): raise ProtocolBufferDecodeError, "corrupted" + return result + return result + + def getFloat(self): + if self.idx + 4 > self.limit: raise ProtocolBufferDecodeError, "truncated" + a = self.buf[self.idx:self.idx+4] + self.idx += 4 + return struct.unpack(" self.limit: raise ProtocolBufferDecodeError, "truncated" + a = self.buf[self.idx:self.idx+8] + self.idx += 8 + return struct.unpack(" self.limit: + raise ProtocolBufferDecodeError, "truncated" + r = self.buf[self.idx : self.idx + length] + self.idx += length + return r.tostring() + + def getRawString(self): + r = self.buf[self.idx:self.limit] + self.idx = self.limit + return r.tostring() + + +class ProtocolBufferDecodeError(Exception): pass +class ProtocolBufferEncodeError(Exception): pass +class ProtocolBufferReturnError(Exception): pass diff --git a/google_appengine/google/net/proto/ProtocolBuffer.pyc b/google_appengine/google/net/proto/ProtocolBuffer.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e20b957cc17c8119b8ca9e26812ddf170f43500e GIT binary patch literal 23422 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 literal 0 HcwPel00001 diff --git a/google_appengine/google/net/proto/message_set.py b/google_appengine/google/net/proto/message_set.py new file mode 100755 index 0000000..b06b7e8 --- /dev/null +++ b/google_appengine/google/net/proto/message_set.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""This module contains the MessageSet class, which is a special kind of +protocol message which can contain other protocol messages without knowing +their types. See the class's doc string for more information.""" + + +from google.net.proto import ProtocolBuffer +import logging + +TAG_BEGIN_ITEM_GROUP = 11 +TAG_END_ITEM_GROUP = 12 +TAG_TYPE_ID = 16 +TAG_MESSAGE = 26 + +class Item: + + def __init__(self, message, message_class=None): + self.message = message + self.message_class = message_class + + def SetToDefaultInstance(self, message_class): + self.message = message_class() + self.message_class = message_class + + def Parse(self, message_class): + + if self.message_class is not None: + return 1 + + try: + self.message = message_class(self.message) + self.message_class = message_class + return 1 + except ProtocolBuffer.ProtocolBufferDecodeError: + logging.warn("Parse error in message inside MessageSet. Tried " + "to parse as: " + message_class.__name__) + return 0 + + def MergeFrom(self, other): + + if self.message_class is not None: + if other.Parse(self.message_class): + self.message.MergeFrom(other.message) + + elif other.message_class is not None: + if not self.Parse(other.message_class): + self.message = other.message_class() + self.message_class = other.message_class + self.message.MergeFrom(other.message) + + else: + self.message += other.message + + def Copy(self): + + if self.message_class is None: + return Item(self.message) + else: + new_message = self.message_class() + new_message.CopyFrom(self.message) + return Item(new_message, self.message_class) + + def Equals(self, other): + + if self.message_class is not None: + if not other.Parse(self.message_class): return 0 + return self.message.Equals(other.message) + + elif other.message_class is not None: + if not self.Parse(other.message_class): return 0 + return self.message.Equals(other.message) + + else: + return self.message == other.message + + def IsInitialized(self, debug_strs=None): + + if self.message_class is None: + return 1 + else: + return self.message.IsInitialized(debug_strs) + + def ByteSize(self, pb, type_id): + + message_length = 0 + if self.message_class is None: + message_length = len(self.message) + else: + message_length = self.message.ByteSize() + + return pb.lengthString(message_length) + pb.lengthVarInt64(type_id) + 2 + + def OutputUnchecked(self, out, type_id): + + out.putVarInt32(TAG_TYPE_ID) + out.putVarUint64(type_id) + out.putVarInt32(TAG_MESSAGE) + if self.message_class is None: + out.putPrefixedString(self.message) + else: + out.putVarInt32(self.message.ByteSize()) + self.message.OutputUnchecked(out) + + def Decode(decoder): + + type_id = 0 + message = None + while 1: + tag = decoder.getVarInt32() + if tag == TAG_END_ITEM_GROUP: + break + if tag == TAG_TYPE_ID: + type_id = decoder.getVarUint64() + continue + if tag == TAG_MESSAGE: + message = decoder.getPrefixedString() + continue + if tag == 0: raise ProtocolBuffer.ProtocolBufferDecodeError + decoder.skipData(tag) + + if type_id == 0 or message is None: + raise ProtocolBuffer.ProtocolBufferDecodeError + return (type_id, message) + Decode = staticmethod(Decode) + + +class MessageSet(ProtocolBuffer.ProtocolMessage): + + def __init__(self, contents=None): + self.items = dict() + if contents is not None: self.MergeFromString(contents) + + + def get(self, message_class): + + if message_class.MESSAGE_TYPE_ID not in self.items: + return message_class() + item = self.items[message_class.MESSAGE_TYPE_ID] + if item.Parse(message_class): + return item.message + else: + return message_class() + + def mutable(self, message_class): + + if message_class.MESSAGE_TYPE_ID not in self.items: + message = message_class() + self.items[message_class.MESSAGE_TYPE_ID] = Item(message, message_class) + return message + item = self.items[message_class.MESSAGE_TYPE_ID] + if not item.Parse(message_class): + item.SetToDefaultInstance(message_class) + return item.message + + def has(self, message_class): + + if message_class.MESSAGE_TYPE_ID not in self.items: + return 0 + item = self.items[message_class.MESSAGE_TYPE_ID] + return item.Parse(message_class) + + def has_unparsed(self, message_class): + return message_class.MESSAGE_TYPE_ID in self.items + + def GetTypeIds(self): + return self.items.keys() + + def NumMessages(self): + return len(self.items) + + def remove(self, message_class): + if message_class.MESSAGE_TYPE_ID in self.items: + del self.items[message_class.MESSAGE_TYPE_ID] + + + def __getitem__(self, message_class): + if message_class.MESSAGE_TYPE_ID not in self.items: + raise KeyError(message_class) + item = self.items[message_class.MESSAGE_TYPE_ID] + if item.Parse(message_class): + return item.message + else: + raise KeyError(message_class) + + def __setitem__(self, message_class, message): + self.items[message_class.MESSAGE_TYPE_ID] = Item(message, message_class) + + def __contains__(self, message_class): + return self.has(message_class) + + def __delitem__(self, message_class): + self.remove(message_class) + + def __len__(self): + return len(self.items) + + + def MergeFrom(self, other): + + assert other is not self + + for (type_id, item) in other.items.items(): + if type_id in self.items: + self.items[type_id].MergeFrom(item) + else: + self.items[type_id] = item.Copy() + + def Equals(self, other): + if other is self: return 1 + if len(self.items) != len(other.items): return 0 + + for (type_id, item) in other.items.items(): + if type_id not in self.items: return 0 + if not self.items[type_id].Equals(item): return 0 + + return 1 + + def __eq__(self, other): + return ((other is not None) + and (other.__class__ == self.__class__) + and self.Equals(other)) + + def __ne__(self, other): + return not (self == other) + + def IsInitialized(self, debug_strs=None): + + initialized = 1 + for item in self.items.values(): + if not item.IsInitialized(debug_strs): + initialized = 0 + return initialized + + def ByteSize(self): + n = 2 * len(self.items) + for (type_id, item) in self.items.items(): + n += item.ByteSize(self, type_id) + return n + + def Clear(self): + self.items = dict() + + def OutputUnchecked(self, out): + for (type_id, item) in self.items.items(): + out.putVarInt32(TAG_BEGIN_ITEM_GROUP) + item.OutputUnchecked(out, type_id) + out.putVarInt32(TAG_END_ITEM_GROUP) + + def TryMerge(self, decoder): + while decoder.avail() > 0: + tag = decoder.getVarInt32() + if tag == TAG_BEGIN_ITEM_GROUP: + (type_id, message) = Item.Decode(decoder) + if type_id in self.items: + self.items[type_id].MergeFrom(Item(message)) + else: + self.items[type_id] = Item(message) + continue + if (tag == 0): raise ProtocolBuffer.ProtocolBufferDecodeError + decoder.skipData(tag) + + def __str__(self, prefix="", printElemNumber=0): + text = "" + for (type_id, item) in self.items.items(): + if item.message_class is None: + text += "%s[%d] <\n" % (prefix, type_id) + text += "%s (%d bytes)\n" % (prefix, len(item.message)) + text += "%s>\n" % prefix + else: + text += "%s[%s] <\n" % (prefix, item.message_class.__name__) + text += item.message.__str__(prefix + " ", printElemNumber) + text += "%s>\n" % prefix + return text + +__all__ = ['MessageSet'] diff --git a/google_appengine/google/net/proto/message_set.pyc b/google_appengine/google/net/proto/message_set.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3e8ba3da2c80b7f244705695ca76888e0d18608 GIT binary patch literal 10921 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 E065}wW&i*H literal 0 HcwPel00001 diff --git a/google_appengine/google/pyglib/__init__.py b/google_appengine/google/pyglib/__init__.py new file mode 100755 index 0000000..c33ae80 --- /dev/null +++ b/google_appengine/google/pyglib/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google_appengine/google/pyglib/__init__.pyc b/google_appengine/google/pyglib/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3473bdf20b94cad43ca3f039aaf01e19b3aa54aa GIT binary patch literal 151 zcwW2siI?lSOo~r30~9a%knvjB+{28Lh_kcgiKNDhrC2C37}$j?pH&r2>!%-46x zFVD-#PfRJ+PtVU!&q<9>EGS6LOV7+pg$n2wRHo--Ch5n=XXa&=#K-FuRF-f64YA41 PPbtkwwF6mI3^E!37a=34 literal 0 HcwPel00001 diff --git a/google_appengine/google/pyglib/gexcept.py b/google_appengine/google/pyglib/gexcept.py new file mode 100644 index 0000000..871af78 --- /dev/null +++ b/google_appengine/google/pyglib/gexcept.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# +# Copyright 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Generic exceptions. +""" + +class TimeoutException(Exception): + def __init__(self, msg=""): + Exception.__init__(self, msg) + +class NestedException(Exception): + def __init__(self, exc_info): + Exception.__init__(self, exc_info[1]) + self.exc_info_ = exc_info + def exc_info(self): + return self.exc_info_ + +class AbstractMethod(Exception): + """Raise this exception to indicate that a method is abstract. Example: + class Foo: + def Bar(self): + raise gexcept.AbstractMethod""" + def __init__(self): + Exception.__init__(self) diff --git a/google_appengine/google/pyglib/gexcept.pyc b/google_appengine/google/pyglib/gexcept.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68e30a79e28ee6bcccc46f7fa1adf29340245234 GIT binary patch literal 1772 zcwV(sOOMkq5FV%5mMw2-DdHhyE+~6x?+Ec&Shy@bq+Hlb;4M< z6~BTzzknI%RVdurNEsz#&&)UTefE!ExBEYz%99kTr-ScXeC-Ju32=&*fnqRaAOpAt zSOI)J4M28kZ`gP{Aopr-xABG`doYP^P}T=%`kz#x7CPnXGF5Y@O<{+9cMn&-=vqpZ1|MO4Zg`v%SE+~jIhqzhq2AI!HRGeX+Y%$QlG z5;32vVx|jKIY#rVS*FiMv$Fr;{3@ob4=EWH0~XPLg`yC-N)p%iKe>??m@)AIEr#s@ zp+tikNSC?0G$Li4lAEN^_&h0(VT}}Ioz%hnN2jKyVTA_iS$PhBvVswhh?%8#BmsDe zC}gaulAlRT|Gs(87~)2jrluq*rrdsNwSUXQp}J2fS?Jq8U@5D{!0N7gXb`?`Tjz|V@eEq=P>HxphR}G_e$OuSLenPt{YbiOzvJ%U$GbD@79w?@l{+_bQ!pg; zMlk>jb8Xv;f;+=?A$2MoEeXd3&wUY&+eB4%$ocW5$mf}Q+2_@hX2M$j z(U{i7xl~hrBo+~!?8Eg1&KJIV-RWNq)sz!fPy2mX6Z48~+%K(eccubL{sR7|0!JoE zq)B&pdECStbgWA;gk3g{_6WeOW&%{oYsT;00S15kUzOM7Yr6O?ji4J0!a?shH1U0u literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/AUTHORS b/google_appengine/lib/antlr3/AUTHORS new file mode 100755 index 0000000..01e79ee --- /dev/null +++ b/google_appengine/lib/antlr3/AUTHORS @@ -0,0 +1,2 @@ +Benjamin Niemann : Main developer of Python target. +Clinton Roy : AST templates and runtime. diff --git a/google_appengine/lib/antlr3/LICENSE b/google_appengine/lib/antlr3/LICENSE new file mode 100755 index 0000000..1d1d5d6 --- /dev/null +++ b/google_appengine/lib/antlr3/LICENSE @@ -0,0 +1,26 @@ +[The "BSD licence"] +Copyright (c) 2003-2006 Terence Parr +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/google_appengine/lib/antlr3/MANIFEST.in b/google_appengine/lib/antlr3/MANIFEST.in new file mode 100755 index 0000000..29c4ad6 --- /dev/null +++ b/google_appengine/lib/antlr3/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE AUTHORS ez_setup.py + diff --git a/google_appengine/lib/antlr3/OWNERS b/google_appengine/lib/antlr3/OWNERS new file mode 100644 index 0000000..2e85b96 --- /dev/null +++ b/google_appengine/lib/antlr3/OWNERS @@ -0,0 +1,7 @@ +# Primary owners +arb +bslatkin +guido + +# Backup +kgibbs diff --git a/google_appengine/lib/antlr3/README b/google_appengine/lib/antlr3/README new file mode 100755 index 0000000..98a50bb --- /dev/null +++ b/google_appengine/lib/antlr3/README @@ -0,0 +1,90 @@ +1) ABOUT +======== + +This is the Python package 'antlr3', which is required to use parsers created +by the ANTLR3 tool. See for more information about +ANTLR3. + + +2) STATUS +========= + +The Python target for ANTLR3 is still in beta. Documentation is lacking, some +bits of the code is not yet done, some functionality has not been tested yet. +Also the API might change a bit - it currently mimics the Java implementation, +but it may be made a bit more pythonic here and there. + +WARNING: Currently the runtime library for V3.1 is not compatible with +recognizers generated by ANTLR V3.0.x. If you are an application developer, +then the suggested way to solve this is to package the correct runtime with +your application. Installing the runtime in the global site-packages directory +may not be a good idea. +It is still undetermined, if a future release of the V3.1 runtime will be +compatible with V3.0.x recognizers or if future runtimes V3.2+ will be +compatible with V3.1 recognizers. +Sorry for the inconvenience. + + +3) DOWNLOAD +=========== + +This runtime is part of the ANTLR distribution. The latest version can be found +at . + +If you are interested in the latest, most bleeding edge version, have a look at +the perforce depot at . There are +tarballs ready to download, so you don't have to install the perforce client. + + +4) INSTALLATION +=============== + +Just like any other Python package: +$ python setup.py install + +See for more information. + + +5) DOCUMENTATION +================ + +Documentation (as far as it exists) can be found in the wiki + + + +6) REPORTING BUGS +================= + +Please send bug reports to the ANTLR mailing list + or +. + +Existing bugs may appear someday in the bugtracker: + + + +7) HACKING +========== + +Only the runtime package can be found here. There are also some StringTemplate +files in 'src/org/antlr/codegen/templates/Python/' and some Java code in +'src/org/antlr/codegen/PythonTarget.java' (of the main ANTLR3 source +distribution). + +If there are no directories 'tests' and 'unittests' in 'runtime/Python', you +should fetch the latest ANTLR3 version from the perforce depot. See section +DOWNLOAD. +You'll need java and ant in order to compile and use the tool. +Be sure to properly setup your CLASSPATH. +(FIXME: is there some generic information, how to build it yourself? I should +point to it to avoid duplication.) + +You can then use the commands +$ python setup.py unittest +$ python setup.py functest +to ensure that changes do not break existing behaviour. + +Please send patches to . For larger code contributions you'll +have to sign the "Developer's Certificate of Origin", which can be found on + or use the feedback form at +. diff --git a/google_appengine/lib/antlr3/antlr3/__init__.py b/google_appengine/lib/antlr3/antlr3/__init__.py new file mode 100755 index 0000000..10e22f0 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/__init__.py @@ -0,0 +1,171 @@ +""" @package antlr3 +@brief ANTLR3 runtime package + +This module contains all support classes, which are needed to use recognizers +generated by ANTLR3. + +@mainpage + +\note Please be warned that the line numbers in the API documentation do not +match the real locations in the source code of the package. This is an +unintended artifact of doxygen, which I could only convince to use the +correct module names by concatenating all files from the package into a single +module file... + +Here is a little overview over the most commonly used classes provided by +this runtime: + +@section recognizers Recognizers + +These recognizers are baseclasses for the code which is generated by ANTLR3. + +- BaseRecognizer: Base class with common recognizer functionality. +- Lexer: Base class for lexers. +- Parser: Base class for parsers. +- tree.TreeParser: Base class for %tree parser. + +@section streams Streams + +Each recognizer pulls its input from one of the stream classes below. Streams +handle stuff like buffering, look-ahead and seeking. + +A character stream is usually the first element in the pipeline of a typical +ANTLR3 application. It is used as the input for a Lexer. + +- ANTLRStringStream: Reads from a string objects. The input should be a unicode + object, or ANTLR3 will have trouble decoding non-ascii data. +- ANTLRFileStream: Opens a file and read the contents, with optional character + decoding. +- ANTLRInputStream: Reads the date from a file-like object, with optional + character decoding. + +A Parser needs a TokenStream as input (which in turn is usually fed by a +Lexer): + +- CommonTokenStream: A basic and most commonly used TokenStream + implementation. +- TokenRewriteStream: A modification of CommonTokenStream that allows the + stream to be altered (by the Parser). See the 'tweak' example for a usecase. + +And tree.TreeParser finally fetches its input from a tree.TreeNodeStream: + +- tree.CommonTreeNodeStream: A basic and most commonly used tree.TreeNodeStream + implementation. + + +@section tokenstrees Tokens and Trees + +A Lexer emits Token objects which are usually buffered by a TokenStream. A +Parser can build a Tree, if the output=AST option has been set in the grammar. + +The runtime provides these Token implementations: + +- CommonToken: A basic and most commonly used Token implementation. +- ClassicToken: A Token object as used in ANTLR 2.x, used to %tree + construction. + +Tree objects are wrapper for Token objects. + +- tree.CommonTree: A basic and most commonly used Tree implementation. + +A tree.TreeAdaptor is used by the parser to create tree.Tree objects for the +input Token objects. + +- tree.CommonTreeAdaptor: A basic and most commonly used tree.TreeAdaptor +implementation. + + +@section Exceptions + +RecognitionException are generated, when a recognizer encounters incorrect +or unexpected input. + +- RecognitionException + - MismatchedRangeException + - MismatchedSetException + - MismatchedNotSetException + . + - MismatchedTokenException + - MismatchedTreeNodeException + - NoViableAltException + - EarlyExitException + - FailedPredicateException + . +. + +A tree.RewriteCardinalityException is raised, when the parsers hits a +cardinality mismatch during AST construction. Although this is basically a +bug in your grammar, it can only be detected at runtime. + +- tree.RewriteCardinalityException + - tree.RewriteEarlyExitException + - tree.RewriteEmptyStreamException + . +. + +""" + +# tree.RewriteRuleElementStream +# tree.RewriteRuleSubtreeStream +# tree.RewriteRuleTokenStream +# CharStream +# DFA +# TokenSource + +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +__version__ = '3.1.1' + +def version_str_to_tuple(version_str): + import re + import sys + + if version_str == 'HEAD': + return (sys.maxint, sys.maxint, sys.maxint, sys.maxint) + + m = re.match(r'(\d+)\.(\d+)(\.(\d+))?(b(\d+))?', version_str) + if m is None: + raise ValueError("Bad version string %r" % version_str) + + major = int(m.group(1)) + minor = int(m.group(2)) + patch = int(m.group(4) or 0) + beta = int(m.group(6) or sys.maxint) + + return (major, minor, patch, beta) + + +runtime_version_str = __version__ +runtime_version = version_str_to_tuple(runtime_version_str) + + +from constants import * +from dfa import * +from exceptions import * +from recognizers import * +from streams import * +from tokens import * diff --git a/google_appengine/lib/antlr3/antlr3/__init__.pyc b/google_appengine/lib/antlr3/antlr3/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29765edd6245413f64371a4e329da419f2f172f3 GIT binary patch literal 4497 zcwVhoTW{Pp7M8}hL`_g&n-qO$Q38vilU-YGn>VAEOcECjw(AXSqYp)a0Y~D@(3VI+ zq&%L&d1?FJ-?o2aANP5I?srH^qp_X!%i10d#mhP0`Obx;|M~BS$@35Y^S3MtKRft5 z!^h0<$fM{-JmcucILZgnZ+IO<0rGN??G`in>&qp~Y3+UmIZcr>t%@ZPbs-gG2MQoM$#e}SY|EwMr7*+-<^q>cz@}!7*{Lir zG_7^n#{|@n#+kviLL@;9%}rBq_?-ap1Q;AyixGVx>I*3ruHgig+Q419EFDJR%3J5L zs@4}WcWy~6nI7Wc0M0Q$O|16($c}qHMTm%XH@hI1z=f^&srCpQ)t)ctxIGy5*mJC0 zDL8QN9w}Q$I}axG6tZcf97Yb?mnlSiFD|dGBnSod8XAAiYjewl%5}RLwiY5i#p5kg zKcNX>LE2-o0-q$`Rw%2IIgK_ug=>b!%Y@V0nuU z5K24`(as4RWr3cYflfnd*h(^UmtLp|oFOAAk`fjs?lPzeuoe;-H0Kvcx>`3AaFv7m zoTMtP_PEKUWI4AyZQ*>26l%fzqY_l!Tv|B}Ya-K!E)@Ywb&g_CS3cfW!~#vLsT2K* zpsyQBvY=kH7L%qu=g8KUHTjUba@|TkJnG}p)mlP)s?UY;s>lVNv|-3KxU;EM-{(`G zbv$vtd2&F`dExS^C+L8UD2-+2sJNwk^yUJnEGr*BJ}4m3jUS6eEv;CIMCp>#5IPiE zH}v{?51e}82!repwWxp;0R`BA`r*U}uczV(vP8IA%RaRW!OuTs;*t}l5Qc!9A&H4S zm~$<`;RqF&!LA{0WGG)PI6~|NYm&B71RK^z@1pM(iMP=5eQFmq}lybYsEISEG~D5UWT! zs#efwL{~v(H7`p}xlftg9WMNC<9v&v>T#}{ZLUY;on+o7g<|jp6;hdXLi(&wrntp` z>k^iIkzVcw=V)K!DFZc8-eg{6LOgV|5}}J4#gIY>VYWtkdP{uVZ3v0KZU8`s8;RF%N6Mv2P`ZRbwd=PbHS1w6XP{w|F>ABy~#tC@J) z;EAyPq4mKdZEuXH*916{-zL4y{q^x9{e$G#xkiOuA2{T7u{^w#*Sf#u*hBKy$Yd(2 zYr@iG6*{2~zuI-U5qG#%lqcrt8GwS*_xY8@+s1uSpeeoa+iElC)#W5b@& z@!KDyT>!bV!;?ufql{bP{M}Xv)D`QBjf<)Y?b_D+5<5Yt8>jjA4RdsId@ummq3Rg+=iU*{Ly8_-T#?D^6!`aR{ZrD WNhU_O|GYi8|K4`|U?Yj!-~R#?x0uKP literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/compat.py b/google_appengine/lib/antlr3/antlr3/compat.py new file mode 100755 index 0000000..b29afca --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/compat.py @@ -0,0 +1,48 @@ +"""Compatibility stuff""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +try: + set = set + frozenset = frozenset +except NameError: + from sets import Set as set, ImmutableSet as frozenset + + +try: + reversed = reversed +except NameError: + def reversed(l): + l = l[:] + l.reverse() + return l + + diff --git a/google_appengine/lib/antlr3/antlr3/compat.pyc b/google_appengine/lib/antlr3/antlr3/compat.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64db12666db818fb21861139483fc290148ac2dc GIT binary patch literal 595 zcwV(oJx{|h5IrYpQX)VVh>3za1|gAw6(Q8ZfRH-011L+B#%>8Xc4WJtAei6}@>iIV z;G7cp1)Oy6+2?m(e*f4AhyBm19Db~eeul0k0tH9_>K-rx2?6Fn;=r{y0FSwF7lPaa zf<_5Y2kt%K9V_F$l@Wn{Q-{C;NCM32|7P`8SuVVReQJc%gK5;Ujkgg-vaB*w%nDwZ zdZ5j6J}m3(og~LiUK@MBP$y> zXaWSq9+=W>6|le~`x_1DAwqmBOBRbUbgLg0nQMDkc_%U@p{?uN>Lef`8AsiAk~F0)qP>l?l3gezmGM>@ mJ+Xx*cI=7QPW?agpOlg=IXktz^cI3YaEXr_Y?58qZTK6=o`2Z@ literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/constants.py b/google_appengine/lib/antlr3/antlr3/constants.py new file mode 100755 index 0000000..bf4a47a --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/constants.py @@ -0,0 +1,57 @@ +"""ANTLR3 runtime package""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +EOF = -1 + +## All tokens go to the parser (unless skip() is called in that rule) +# on a particular "channel". The parser tunes to a particular channel +# so that whitespace etc... can go to the parser on a "hidden" channel. +DEFAULT_CHANNEL = 0 + +## Anything on different channel than DEFAULT_CHANNEL is not parsed +# by parser. +HIDDEN_CHANNEL = 99 + +# Predefined token types +EOR_TOKEN_TYPE = 1 + +## +# imaginary tree navigation type; traverse "get child" link +DOWN = 2 +## +#imaginary tree navigation type; finish with a child list +UP = 3 + +MIN_TOKEN_TYPE = UP+1 + +INVALID_TOKEN_TYPE = 0 + diff --git a/google_appengine/lib/antlr3/antlr3/constants.pyc b/google_appengine/lib/antlr3/antlr3/constants.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b56baccbb593f71badb3a2e525abcffcf97bb7a GIT binary patch literal 423 zcwSYI&r8EF6vyA%E}H{?;6X1QcHGGxc9mhZ87r&ln4MxSr8c1kyQZue;-Bfi;#ts_ zp{NhOpBKKo|r&Oy tuhgTT*QVNxe_EmI*5J84Z(faE(#2YfZ6zmm1MLxl3`v)G#NEjmIA17iT}J=_ literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/dfa.py b/google_appengine/lib/antlr3/antlr3/dfa.py new file mode 100755 index 0000000..ff93761 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/dfa.py @@ -0,0 +1,213 @@ +"""ANTLR3 runtime package""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licensc] + +from antlr3.constants import EOF +from antlr3.exceptions import NoViableAltException, BacktrackingFailed + + +class DFA(object): + """@brief A DFA implemented as a set of transition tables. + + Any state that has a semantic predicate edge is special; those states + are generated with if-then-else structures in a specialStateTransition() + which is generated by cyclicDFA template. + + """ + + def __init__( + self, + recognizer, decisionNumber, + eot, eof, min, max, accept, special, transition + ): + ## Which recognizer encloses this DFA? Needed to check backtracking + self.recognizer = recognizer + + self.decisionNumber = decisionNumber + self.eot = eot + self.eof = eof + self.min = min + self.max = max + self.accept = accept + self.special = special + self.transition = transition + + + def predict(self, input): + """ + From the input stream, predict what alternative will succeed + using this DFA (representing the covering regular approximation + to the underlying CFL). Return an alternative number 1..n. Throw + an exception upon error. + """ + mark = input.mark() + s = 0 # we always start at s0 + try: + for _ in xrange(50000): + #print "***Current state = %d" % s + + specialState = self.special[s] + if specialState >= 0: + #print "is special" + s = self.specialStateTransition(specialState, input) + if s == -1: + self.noViableAlt(s, input) + return 0 + input.consume() + continue + + if self.accept[s] >= 1: + #print "accept state for alt %d" % self.accept[s] + return self.accept[s] + + # look for a normal char transition + c = input.LA(1) + + #print "LA = %d (%r)" % (c, unichr(c) if c >= 0 else 'EOF') + #print "range = %d..%d" % (self.min[s], self.max[s]) + + if c >= self.min[s] and c <= self.max[s]: + # move to next state + snext = self.transition[s][c-self.min[s]] + #print "in range, next state = %d" % snext + + if snext < 0: + #print "not a normal transition" + # was in range but not a normal transition + # must check EOT, which is like the else clause. + # eot[s]>=0 indicates that an EOT edge goes to another + # state. + if self.eot[s] >= 0: # EOT Transition to accept state? + #print "EOT trans to accept state %d" % self.eot[s] + + s = self.eot[s] + input.consume() + # TODO: I had this as return accept[eot[s]] + # which assumed here that the EOT edge always + # went to an accept...faster to do this, but + # what about predicated edges coming from EOT + # target? + continue + + #print "no viable alt" + self.noViableAlt(s, input) + return 0 + + s = snext + input.consume() + continue + + if self.eot[s] >= 0: + #print "EOT to %d" % self.eot[s] + + s = self.eot[s] + input.consume() + continue + + # EOF Transition to accept state? + if c == EOF and self.eof[s] >= 0: + #print "EOF Transition to accept state %d" \ + # % self.accept[self.eof[s]] + return self.accept[self.eof[s]] + + # not in range and not EOF/EOT, must be invalid symbol + self.noViableAlt(s, input) + return 0 + + else: + raise RuntimeError("DFA bang!") + + finally: + input.rewind(mark) + + + def noViableAlt(self, s, input): + if self.recognizer._state.backtracking > 0: + raise BacktrackingFailed + + nvae = NoViableAltException( + self.getDescription(), + self.decisionNumber, + s, + input + ) + + self.error(nvae) + raise nvae + + + def error(self, nvae): + """A hook for debugging interface""" + pass + + + def specialStateTransition(self, s, input): + return -1 + + + def getDescription(self): + return "n/a" + + +## def specialTransition(self, state, symbol): +## return 0 + + + def unpack(cls, string): + """@brief Unpack the runlength encoded table data. + + Terence implemented packed table initializers, because Java has a + size restriction on .class files and the lookup tables can grow + pretty large. The generated JavaLexer.java of the Java.g example + would be about 15MB with uncompressed array initializers. + + Python does not have any size restrictions, but the compilation of + such large source files seems to be pretty memory hungry. The memory + consumption of the python process grew to >1.5GB when importing a + 15MB lexer, eating all my swap space and I was to impacient to see, + if it could finish at all. With packed initializers that are unpacked + at import time of the lexer module, everything works like a charm. + + """ + + ret = [] + for i in range(len(string) / 2): + (n, v) = ord(string[i*2]), ord(string[i*2+1]) + + # Is there a bitwise operation to do this? + if v == 0xFFFF: + v = -1 + + ret += [v] * n + + return ret + + unpack = classmethod(unpack) diff --git a/google_appengine/lib/antlr3/antlr3/dfa.pyc b/google_appengine/lib/antlr3/antlr3/dfa.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac410a41bbdd2e4ae5f65317b41948d7e17fef09 GIT binary patch literal 4620 zcwV(vUvC_@5hr)=kM1nheegOyV!DvASdm$yf0 zm%MPfI$dA^MO~m@p+G-CU;5C;qObh~1^U(x(DyzUXlI72)2WUh3aBR?j~tT2nc>WD zhJXF%M*qn>|N3#tnok?PkKtSGLX$Ce2rXw>#Ev;DB6b)tj^mge$E;|v!xk&r?63{g z7E>KIfw?yOHDj+B`|7aEvJN{Kbdld3Xm<9W?#w z)Ync`LK{3Ja5_K^&zI5-J|t%IsZOVO-UY|;LZpkd&?&0v73c}%A+4q+?Sn!3jr~7G z<9q?lOU_<0HiAw>9T>*cfnkd}Fl<@bAEqJ0+ zsm+c4Mmhf-bYD=c0$uj&attK&4`qGxJE7lFoA4`7eFuJJicaRqV}o6Y@x`qTXxPGe zRZJ4>Bp5RZ?oM!Df)0_OObM<{u3@td&48UZ)eoTAp4w7vo773W{nXA)VP#fr=ho(h z8p+vAnOqySUFh*PI6>h)I{(QgayVOfJZ3b~M*Godh)gjoSf>~D`08JZ*Ra-n={ll_ zu0vCho>4fQ#q68E^V7d^Pq7ZZ{qz@q=4W&M{Ech<)Hfwf0%W}h*RGvUfOdlexE zSOsx(6ft`{OtJWPfJY>ASN(&ph7?dE*C9dF+|>!M4c5{%i;!oq9e{GiyotF$wc{&k68*Q?9kwDy1G z-eG>7O!z@qy0N~~ZKOh)b?N^+=00_gAu4C*0iGO*4%Kw0dq82~9 z&X#~DfG=iNdjtw4%dG}{JYWF;69j~EMtXgs09%SeR5gT*%KE*!0w{*<02YW01E*kP z1qc|(N~HEgIUG2Z*M)QfoZQ({T}lkBe$QK4R+~(@Vu1@k-QD|eC`6(Ft^lgcrPB>X zwRkif8d&pu>g>GV!$s;mQHXj5jdISqVZWy_JoM*(fd&(fM(n6COi|jgH2H)5!Hx6a zE~PsrKsbfS&lLr+#}xMMXz6j@T;wVOkkp#0E)}7G$0*#}S-}QGga8^S7a@lyT1bx1 zP->FEskt^8rI};{S0UWc4JFrcNP18^A_l7x5;>(El~JeuiXjMvZoK0$^4vqt-F4pK zG2dXFc!O_5xA+FX!P~sgyU}K}9$#+{qIcqr&L+(EJKY%OqE47a3h%}b;@`bYv1~Vi#~+rm z@su}my+O7LB{JJGNWss=yJDdP@c843Q25hnz9iHyp~)%TpH|EhY+%0NDCup=_dmzz z0GxVBr5TVn?*aW10ZN0rNQ|H|XzGEG@cUH%1BsGYgfltE!fx}saN^hZ*$W^Z3QZ@)JeKRR9s=~k8nD_D zW0lGpYTh5o6B%miC8r9OKqi7D2k3>#0{(|-A*)JE;5h+HO-7Ox5DWFJsn$gb2`0x> zbIu3w&-;ade3q*rq@mT~8;{*nr^*eFPyj6dr$Lxu4rxlF5=%yNTNhAWD`gr%t9udHD}PVvmh!I z04XR2CRKCf`pKi=$De`T@LIt;h7^d{b8%~Al>$w=C6o-4P=u7A;#|()Edu~WH}^v^ zmn0`}k*Nkx!2w9WwdAEILVJ)6%{~E(R#QO*PB9cO(C&?2t=c*~&hUj*n=rlP2J|4X zpto(KCW$Iao7IH^iJ<@jy;179b;p$`^f8Dl(y4UiaLMXILJVUnQ~=?cx`hyqQVUfk z^m>8Fpi@#`4h6lii+Lr@Vl(TA7X-! zZbt*Ero#Kh$Wo0)^vD{Gf}LR4rD0~%(I~;}n%u_*Ga97^!eGL@WD8kcBXuEkO9ft) zU1)S=Y9(mC_gRxxJKavyyVd)$cfHr{wR&s4`}?72*hXnk<*<2(j_;vgQIY3wUd$|u l)QV~dswT*Gc6_8#Pv`v~^dPH$KiK&bsvIdxh#UM){2xPwL{|U+ literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/dottreegen.py b/google_appengine/lib/antlr3/antlr3/dottreegen.py new file mode 100755 index 0000000..827d4ec --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/dottreegen.py @@ -0,0 +1,210 @@ +""" @package antlr3.dottreegenerator +@brief ANTLR3 runtime package, tree module + +This module contains all support classes for AST construction and tree parsers. + +""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +# lot's of docstrings are missing, don't complain for now... +# pylint: disable-msg=C0111 + +from antlr3.tree import CommonTreeAdaptor +import stringtemplate3 + +class DOTTreeGenerator(object): + """ + A utility class to generate DOT diagrams (graphviz) from + arbitrary trees. You can pass in your own templates and + can pass in any kind of tree or use Tree interface method. + """ + + _treeST = stringtemplate3.StringTemplate( + template=( + "digraph {\n" + + " ordering=out;\n" + + " ranksep=.4;\n" + + " node [shape=plaintext, fixedsize=true, fontsize=11, fontname=\"Courier\",\n" + + " width=.25, height=.25];\n" + + " edge [arrowsize=.5]\n" + + " $nodes$\n" + + " $edges$\n" + + "}\n") + ) + + _nodeST = stringtemplate3.StringTemplate( + template="$name$ [label=\"$text$\"];\n" + ) + + _edgeST = stringtemplate3.StringTemplate( + template="$parent$ -> $child$ // \"$parentText$\" -> \"$childText$\"\n" + ) + + def __init__(self): + ## Track node to number mapping so we can get proper node name back + self.nodeToNumberMap = {} + + ## Track node number so we can get unique node names + self.nodeNumber = 0 + + + def toDOT(self, tree, adaptor=None, treeST=_treeST, edgeST=_edgeST): + if adaptor is None: + adaptor = CommonTreeAdaptor() + + treeST = treeST.getInstanceOf() + + self.nodeNumber = 0 + self.toDOTDefineNodes(tree, adaptor, treeST) + + self.nodeNumber = 0 + self.toDOTDefineEdges(tree, adaptor, treeST, edgeST) + return treeST + + + def toDOTDefineNodes(self, tree, adaptor, treeST, knownNodes=None): + if knownNodes is None: + knownNodes = set() + + if tree is None: + return + + n = adaptor.getChildCount(tree) + if n == 0: + # must have already dumped as child from previous + # invocation; do nothing + return + + # define parent node + number = self.getNodeNumber(tree) + if number not in knownNodes: + parentNodeST = self.getNodeST(adaptor, tree) + treeST.setAttribute("nodes", parentNodeST) + knownNodes.add(number) + + # for each child, do a " [label=text]" node def + for i in range(n): + child = adaptor.getChild(tree, i) + + number = self.getNodeNumber(child) + if number not in knownNodes: + nodeST = self.getNodeST(adaptor, child) + treeST.setAttribute("nodes", nodeST) + knownNodes.add(number) + + self.toDOTDefineNodes(child, adaptor, treeST, knownNodes) + + + def toDOTDefineEdges(self, tree, adaptor, treeST, edgeST): + if tree is None: + return + + n = adaptor.getChildCount(tree) + if n == 0: + # must have already dumped as child from previous + # invocation; do nothing + return + + parentName = "n%d" % self.getNodeNumber(tree) + + # for each child, do a parent -> child edge using unique node names + parentText = adaptor.getText(tree) + for i in range(n): + child = adaptor.getChild(tree, i) + childText = adaptor.getText(child) + childName = "n%d" % self.getNodeNumber(child) + edgeST = edgeST.getInstanceOf() + edgeST.setAttribute("parent", parentName) + edgeST.setAttribute("child", childName) + edgeST.setAttribute("parentText", parentText) + edgeST.setAttribute("childText", childText) + treeST.setAttribute("edges", edgeST) + self.toDOTDefineEdges(child, adaptor, treeST, edgeST) + + + def getNodeST(self, adaptor, t): + text = adaptor.getText(t) + nodeST = self._nodeST.getInstanceOf() + uniqueName = "n%d" % self.getNodeNumber(t) + nodeST.setAttribute("name", uniqueName) + if text is not None: + text = text.replace('"', r'\\"') + nodeST.setAttribute("text", text) + return nodeST + + + def getNodeNumber(self, t): + try: + return self.nodeToNumberMap[t] + except KeyError: + self.nodeToNumberMap[t] = self.nodeNumber + self.nodeNumber += 1 + return self.nodeNumber - 1 + + +def toDOT(tree, adaptor=None, treeST=DOTTreeGenerator._treeST, edgeST=DOTTreeGenerator._edgeST): + """ + Generate DOT (graphviz) for a whole tree not just a node. + For example, 3+4*5 should generate: + + digraph { + node [shape=plaintext, fixedsize=true, fontsize=11, fontname="Courier", + width=.4, height=.2]; + edge [arrowsize=.7] + "+"->3 + "+"->"*" + "*"->4 + "*"->5 + } + + Return the ST not a string in case people want to alter. + + Takes a Tree interface object. + + Example of invokation: + + import antlr3 + import antlr3.extras + + input = antlr3.ANTLRInputStream(sys.stdin) + lex = TLexer(input) + tokens = antlr3.CommonTokenStream(lex) + parser = TParser(tokens) + tree = parser.e().tree + print tree.toStringTree() + st = antlr3.extras.toDOT(t) + print st + + """ + + gen = DOTTreeGenerator() + return gen.toDOT(tree, adaptor, treeST, edgeST) diff --git a/google_appengine/lib/antlr3/antlr3/exceptions.py b/google_appengine/lib/antlr3/antlr3/exceptions.py new file mode 100755 index 0000000..97b1074 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/exceptions.py @@ -0,0 +1,364 @@ +"""ANTLR3 exception hierarchy""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +from antlr3.constants import INVALID_TOKEN_TYPE + + +class BacktrackingFailed(Exception): + """@brief Raised to signal failed backtrack attempt""" + + pass + + +class RecognitionException(Exception): + """@brief The root of the ANTLR exception hierarchy. + + To avoid English-only error messages and to generally make things + as flexible as possible, these exceptions are not created with strings, + but rather the information necessary to generate an error. Then + the various reporting methods in Parser and Lexer can be overridden + to generate a localized error message. For example, MismatchedToken + exceptions are built with the expected token type. + So, don't expect getMessage() to return anything. + + Note that as of Java 1.4, you can access the stack trace, which means + that you can compute the complete trace of rules from the start symbol. + This gives you considerable context information with which to generate + useful error messages. + + ANTLR generates code that throws exceptions upon recognition error and + also generates code to catch these exceptions in each rule. If you + want to quit upon first error, you can turn off the automatic error + handling mechanism using rulecatch action, but you still need to + override methods mismatch and recoverFromMismatchSet. + + In general, the recognition exceptions can track where in a grammar a + problem occurred and/or what was the expected input. While the parser + knows its state (such as current input symbol and line info) that + state can change before the exception is reported so current token index + is computed and stored at exception time. From this info, you can + perhaps print an entire line of input not just a single token, for example. + Better to just say the recognizer had a problem and then let the parser + figure out a fancy report. + + """ + + def __init__(self, input=None): + Exception.__init__(self) + + # What input stream did the error occur in? + self.input = None + + # What is index of token/char were we looking at when the error + # occurred? + self.index = None + + # The current Token when an error occurred. Since not all streams + # can retrieve the ith Token, we have to track the Token object. + # For parsers. Even when it's a tree parser, token might be set. + self.token = None + + # If this is a tree parser exception, node is set to the node with + # the problem. + self.node = None + + # The current char when an error occurred. For lexers. + self.c = None + + # Track the line at which the error occurred in case this is + # generated from a lexer. We need to track this since the + # unexpected char doesn't carry the line info. + self.line = None + + self.charPositionInLine = None + + # If you are parsing a tree node stream, you will encounter som + # imaginary nodes w/o line/col info. We now search backwards looking + # for most recent token with line/col info, but notify getErrorHeader() + # that info is approximate. + self.approximateLineInfo = False + + + if input is not None: + self.input = input + self.index = input.index() + + # late import to avoid cyclic dependencies + from antlr3.streams import TokenStream, CharStream + from antlr3.tree import TreeNodeStream + + if isinstance(self.input, TokenStream): + self.token = self.input.LT(1) + self.line = self.token.line + self.charPositionInLine = self.token.charPositionInLine + + if isinstance(self.input, TreeNodeStream): + self.extractInformationFromTreeNodeStream(self.input) + + else: + if isinstance(self.input, CharStream): + self.c = self.input.LT(1) + self.line = self.input.line + self.charPositionInLine = self.input.charPositionInLine + + else: + self.c = self.input.LA(1) + + def extractInformationFromTreeNodeStream(self, nodes): + from antlr3.tree import Tree, CommonTree + from antlr3.tokens import CommonToken + + self.node = nodes.LT(1) + adaptor = nodes.adaptor + payload = adaptor.getToken(self.node) + if payload is not None: + self.token = payload + if payload.line <= 0: + # imaginary node; no line/pos info; scan backwards + i = -1 + priorNode = nodes.LT(i) + while priorNode is not None: + priorPayload = adaptor.getToken(priorNode) + if priorPayload is not None and priorPayload.line > 0: + # we found the most recent real line / pos info + self.line = priorPayload.line + self.charPositionInLine = priorPayload.charPositionInLine + self.approximateLineInfo = True + break + + i -= 1 + priorNode = nodes.LT(i) + + else: # node created from real token + self.line = payload.line + self.charPositionInLine = payload.charPositionInLine + + elif isinstance(self.node, Tree): + self.line = self.node.line + self.charPositionInLine = self.node.charPositionInLine + if isinstance(self.node, CommonTree): + self.token = self.node.token + + else: + type = adaptor.getType(self.node) + text = adaptor.getText(self.node) + self.token = CommonToken(type=type, text=text) + + + def getUnexpectedType(self): + """Return the token type or char of the unexpected input element""" + + from antlr3.streams import TokenStream + from antlr3.tree import TreeNodeStream + + if isinstance(self.input, TokenStream): + return self.token.type + + elif isinstance(self.input, TreeNodeStream): + adaptor = self.input.treeAdaptor + return adaptor.getType(self.node) + + else: + return self.c + + unexpectedType = property(getUnexpectedType) + + +class MismatchedTokenException(RecognitionException): + """@brief A mismatched char or Token or tree node.""" + + def __init__(self, expecting, input): + RecognitionException.__init__(self, input) + self.expecting = expecting + + + def __str__(self): + #return "MismatchedTokenException("+self.expecting+")" + return "MismatchedTokenException(%r!=%r)" % ( + self.getUnexpectedType(), self.expecting + ) + __repr__ = __str__ + + +class UnwantedTokenException(MismatchedTokenException): + """An extra token while parsing a TokenStream""" + + def getUnexpectedToken(self): + return self.token + + + def __str__(self): + exp = ", expected %s" % self.expecting + if self.expecting == INVALID_TOKEN_TYPE: + exp = "" + + if self.token is None: + return "UnwantedTokenException(found=%s%s)" % (None, exp) + + return "UnwantedTokenException(found=%s%s)" % (self.token.text, exp) + __repr__ = __str__ + + +class MissingTokenException(MismatchedTokenException): + """ + We were expecting a token but it's not found. The current token + is actually what we wanted next. + """ + + def __init__(self, expecting, input, inserted): + MismatchedTokenException.__init__(self, expecting, input) + + self.inserted = inserted + + + def getMissingType(self): + return self.expecting + + + def __str__(self): + if self.inserted is not None and self.token is not None: + return "MissingTokenException(inserted %r at %r)" % ( + self.inserted, self.token.text) + + if self.token is not None: + return "MissingTokenException(at %r)" % self.token.text + + return "MissingTokenException" + __repr__ = __str__ + + +class MismatchedRangeException(RecognitionException): + """@brief The next token does not match a range of expected types.""" + + def __init__(self, a, b, input): + RecognitionException.__init__(self, input) + + self.a = a + self.b = b + + + def __str__(self): + return "MismatchedRangeException(%r not in [%r..%r])" % ( + self.getUnexpectedType(), self.a, self.b + ) + __repr__ = __str__ + + +class MismatchedSetException(RecognitionException): + """@brief The next token does not match a set of expected types.""" + + def __init__(self, expecting, input): + RecognitionException.__init__(self, input) + + self.expecting = expecting + + + def __str__(self): + return "MismatchedSetException(%r not in %r)" % ( + self.getUnexpectedType(), self.expecting + ) + __repr__ = __str__ + + +class MismatchedNotSetException(MismatchedSetException): + """@brief Used for remote debugger deserialization""" + + def __str__(self): + return "MismatchedNotSetException(%r!=%r)" % ( + self.getUnexpectedType(), self.expecting + ) + __repr__ = __str__ + + +class NoViableAltException(RecognitionException): + """@brief Unable to decide which alternative to choose.""" + + def __init__( + self, grammarDecisionDescription, decisionNumber, stateNumber, input + ): + RecognitionException.__init__(self, input) + + self.grammarDecisionDescription = grammarDecisionDescription + self.decisionNumber = decisionNumber + self.stateNumber = stateNumber + + + def __str__(self): + return "NoViableAltException(%r!=[%r])" % ( + self.unexpectedType, self.grammarDecisionDescription + ) + __repr__ = __str__ + + +class EarlyExitException(RecognitionException): + """@brief The recognizer did not match anything for a (..)+ loop.""" + + def __init__(self, decisionNumber, input): + RecognitionException.__init__(self, input) + + self.decisionNumber = decisionNumber + + +class FailedPredicateException(RecognitionException): + """@brief A semantic predicate failed during validation. + + Validation of predicates + occurs when normally parsing the alternative just like matching a token. + Disambiguating predicate evaluation occurs when we hoist a predicate into + a prediction decision. + """ + + def __init__(self, input, ruleName, predicateText): + RecognitionException.__init__(self, input) + + self.ruleName = ruleName + self.predicateText = predicateText + + + def __str__(self): + return "FailedPredicateException("+self.ruleName+",{"+self.predicateText+"}?)" + __repr__ = __str__ + + +class MismatchedTreeNodeException(RecognitionException): + """@brief The next tree mode does not match the expected type.""" + + def __init__(self, expecting, input): + RecognitionException.__init__(self, input) + + self.expecting = expecting + + def __str__(self): + return "MismatchedTreeNodeException(%r!=%r)" % ( + self.getUnexpectedType(), self.expecting + ) + __repr__ = __str__ diff --git a/google_appengine/lib/antlr3/antlr3/exceptions.pyc b/google_appengine/lib/antlr3/antlr3/exceptions.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00c0d6e0be720cc4a9edf29763d4568b54829529 GIT binary patch literal 12864 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 ") + except (EOFError, KeyboardInterrupt): + self.stdout.write("\nBye.\n") + break + + inStream = antlr3.ANTLRStringStream(input) + self.parseStream(options, inStream) + + else: + if options.input is not None: + inStream = antlr3.ANTLRStringStream(options.input) + + elif len(args) == 1 and args[0] != '-': + inStream = antlr3.ANTLRFileStream( + args[0], encoding=options.encoding + ) + + else: + inStream = antlr3.ANTLRInputStream( + self.stdin, encoding=options.encoding + ) + + if options.profile: + try: + import cProfile as profile + except ImportError: + import profile + + profile.runctx( + 'self.parseStream(options, inStream)', + globals(), + locals(), + 'profile.dat' + ) + + import pstats + stats = pstats.Stats('profile.dat') + stats.strip_dirs() + stats.sort_stats('time') + stats.print_stats(100) + + elif options.hotshot: + import hotshot + + profiler = hotshot.Profile('hotshot.dat') + profiler.runctx( + 'self.parseStream(options, inStream)', + globals(), + locals() + ) + + else: + self.parseStream(options, inStream) + + + def setUp(self, options): + pass + + + def parseStream(self, options, inStream): + raise NotImplementedError + + + def write(self, options, text): + if not options.no_output: + self.stdout.write(text) + + + def writeln(self, options, text): + self.write(options, text + '\n') + + +class LexerMain(_Main): + def __init__(self, lexerClass): + _Main.__init__(self) + + self.lexerClass = lexerClass + + + def parseStream(self, options, inStream): + lexer = self.lexerClass(inStream) + for token in lexer: + self.writeln(options, str(token)) + + +class ParserMain(_Main): + def __init__(self, lexerClassName, parserClass): + _Main.__init__(self) + + self.lexerClassName = lexerClassName + self.lexerClass = None + self.parserClass = parserClass + + + def setupOptions(self, optParser): + optParser.add_option( + "--lexer", + action="store", + type="string", + dest="lexerClass", + default=self.lexerClassName + ) + optParser.add_option( + "--rule", + action="store", + type="string", + dest="parserRule" + ) + + + def setUp(self, options): + lexerMod = __import__(options.lexerClass) + self.lexerClass = getattr(lexerMod, options.lexerClass) + + + def parseStream(self, options, inStream): + lexer = self.lexerClass(inStream) + tokenStream = antlr3.CommonTokenStream(lexer) + parser = self.parserClass(tokenStream) + result = getattr(parser, options.parserRule)() + if result is not None: + if hasattr(result, 'tree'): + if result.tree is not None: + self.writeln(options, result.tree.toStringTree()) + else: + self.writeln(options, repr(result)) + + +class WalkerMain(_Main): + def __init__(self, walkerClass): + _Main.__init__(self) + + self.lexerClass = None + self.parserClass = None + self.walkerClass = walkerClass + + + def setupOptions(self, optParser): + optParser.add_option( + "--lexer", + action="store", + type="string", + dest="lexerClass", + default=None + ) + optParser.add_option( + "--parser", + action="store", + type="string", + dest="parserClass", + default=None + ) + optParser.add_option( + "--parser-rule", + action="store", + type="string", + dest="parserRule", + default=None + ) + optParser.add_option( + "--rule", + action="store", + type="string", + dest="walkerRule" + ) + + + def setUp(self, options): + lexerMod = __import__(options.lexerClass) + self.lexerClass = getattr(lexerMod, options.lexerClass) + parserMod = __import__(options.parserClass) + self.parserClass = getattr(parserMod, options.parserClass) + + + def parseStream(self, options, inStream): + lexer = self.lexerClass(inStream) + tokenStream = antlr3.CommonTokenStream(lexer) + parser = self.parserClass(tokenStream) + result = getattr(parser, options.parserRule)() + if result is not None: + assert hasattr(result, 'tree'), "Parser did not return an AST" + nodeStream = antlr3.tree.CommonTreeNodeStream(result.tree) + nodeStream.setTokenStream(tokenStream) + walker = self.walkerClass(nodeStream) + result = getattr(walker, options.walkerRule)() + if result is not None: + if hasattr(result, 'tree'): + self.writeln(options, result.tree.toStringTree()) + else: + self.writeln(options, repr(result)) + diff --git a/google_appengine/lib/antlr3/antlr3/recognizers.py b/google_appengine/lib/antlr3/antlr3/recognizers.py new file mode 100755 index 0000000..b94ae87 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/recognizers.py @@ -0,0 +1,1511 @@ +"""ANTLR3 runtime package""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +import sys +import inspect + +from antlr3 import runtime_version, runtime_version_str +from antlr3.constants import DEFAULT_CHANNEL, HIDDEN_CHANNEL, EOF, \ + EOR_TOKEN_TYPE, INVALID_TOKEN_TYPE +from antlr3.exceptions import RecognitionException, MismatchedTokenException, \ + MismatchedRangeException, MismatchedTreeNodeException, \ + NoViableAltException, EarlyExitException, MismatchedSetException, \ + MismatchedNotSetException, FailedPredicateException, \ + BacktrackingFailed, UnwantedTokenException, MissingTokenException +from antlr3.tokens import CommonToken, EOF_TOKEN, SKIP_TOKEN +from antlr3.compat import set, frozenset, reversed + + +class RecognizerSharedState(object): + """ + The set of fields needed by an abstract recognizer to recognize input + and recover from errors etc... As a separate state object, it can be + shared among multiple grammars; e.g., when one grammar imports another. + + These fields are publically visible but the actual state pointer per + parser is protected. + """ + + def __init__(self): + # Track the set of token types that can follow any rule invocation. + # Stack grows upwards. + self.following = [] + + # This is true when we see an error and before having successfully + # matched a token. Prevents generation of more than one error message + # per error. + self.errorRecovery = False + + # The index into the input stream where the last error occurred. + # This is used to prevent infinite loops where an error is found + # but no token is consumed during recovery...another error is found, + # ad naseum. This is a failsafe mechanism to guarantee that at least + # one token/tree node is consumed for two errors. + self.lastErrorIndex = -1 + + # If 0, no backtracking is going on. Safe to exec actions etc... + # If >0 then it's the level of backtracking. + self.backtracking = 0 + + # An array[size num rules] of Map that tracks + # the stop token index for each rule. ruleMemo[ruleIndex] is + # the memoization table for ruleIndex. For key ruleStartIndex, you + # get back the stop token for associated rule or MEMO_RULE_FAILED. + # + # This is only used if rule memoization is on (which it is by default). + self.ruleMemo = None + + ## Did the recognizer encounter a syntax error? Track how many. + self.syntaxErrors = 0 + + + # LEXER FIELDS (must be in same state object to avoid casting + # constantly in generated code and Lexer object) :( + + + ## The goal of all lexer rules/methods is to create a token object. + # This is an instance variable as multiple rules may collaborate to + # create a single token. nextToken will return this object after + # matching lexer rule(s). If you subclass to allow multiple token + # emissions, then set this to the last token to be matched or + # something nonnull so that the auto token emit mechanism will not + # emit another token. + self.token = None + + ## What character index in the stream did the current token start at? + # Needed, for example, to get the text for current token. Set at + # the start of nextToken. + self.tokenStartCharIndex = -1 + + ## The line on which the first character of the token resides + self.tokenStartLine = None + + ## The character position of first character within the line + self.tokenStartCharPositionInLine = None + + ## The channel number for the current token + self.channel = None + + ## The token type for the current token + self.type = None + + ## You can set the text for the current token to override what is in + # the input char buffer. Use setText() or can set this instance var. + self.text = None + + +class BaseRecognizer(object): + """ + @brief Common recognizer functionality. + + A generic recognizer that can handle recognizers generated from + lexer, parser, and tree grammars. This is all the parsing + support code essentially; most of it is error recovery stuff and + backtracking. + """ + + MEMO_RULE_FAILED = -2 + MEMO_RULE_UNKNOWN = -1 + + # copies from Token object for convenience in actions + DEFAULT_TOKEN_CHANNEL = DEFAULT_CHANNEL + + # for convenience in actions + HIDDEN = HIDDEN_CHANNEL + + # overridden by generated subclasses + tokenNames = None + + # The antlr_version attribute has been introduced in 3.1. If it is not + # overwritten in the generated recognizer, we assume a default of 3.0.1. + antlr_version = (3, 0, 1, 0) + antlr_version_str = "3.0.1" + + def __init__(self, state=None): + # Input stream of the recognizer. Must be initialized by a subclass. + self.input = None + + ## State of a lexer, parser, or tree parser are collected into a state + # object so the state can be shared. This sharing is needed to + # have one grammar import others and share same error variables + # and other state variables. It's a kind of explicit multiple + # inheritance via delegation of methods and shared state. + if state is None: + state = RecognizerSharedState() + self._state = state + + if self.antlr_version > runtime_version: + raise RuntimeError( + "ANTLR version mismatch: " + "The recognizer has been generated by V%s, but this runtime " + "is V%s. Please use the V%s runtime or higher." + % (self.antlr_version_str, + runtime_version_str, + self.antlr_version_str)) + elif (self.antlr_version < (3, 1, 0, 0) and + self.antlr_version != runtime_version): + # FIXME: make the runtime compatible with 3.0.1 codegen + # and remove this block. + raise RuntimeError( + "ANTLR version mismatch: " + "The recognizer has been generated by V%s, but this runtime " + "is V%s. Please use the V%s runtime." + % (self.antlr_version_str, + runtime_version_str, + self.antlr_version_str)) + + # this one only exists to shut up pylint :( + def setInput(self, input): + self.input = input + + + def reset(self): + """ + reset the parser's state; subclasses must rewinds the input stream + """ + + # wack everything related to error recovery + if self._state is None: + # no shared state work to do + return + + self._state.following = [] + self._state.errorRecovery = False + self._state.lastErrorIndex = -1 + self._state.syntaxErrors = 0 + # wack everything related to backtracking and memoization + self._state.backtracking = 0 + if self._state.ruleMemo is not None: + self._state.ruleMemo = {} + + + def match(self, input, ttype, follow): + """ + Match current input symbol against ttype. Attempt + single token insertion or deletion error recovery. If + that fails, throw MismatchedTokenException. + + To turn off single token insertion or deletion error + recovery, override mismatchRecover() and have it call + plain mismatch(), which does not recover. Then any error + in a rule will cause an exception and immediate exit from + rule. Rule would recover by resynchronizing to the set of + symbols that can follow rule ref. + """ + + matchedSymbol = self.getCurrentInputSymbol(input) + if self.input.LA(1) == ttype: + self.input.consume() + self._state.errorRecovery = False + return matchedSymbol + + if self._state.backtracking > 0: + # FIXME: need to return matchedSymbol here as well. damn!! + raise BacktrackingFailed + + matchedSymbol = self.recoverFromMismatchedToken(input, ttype, follow) + return matchedSymbol + + + def matchAny(self, input): + """Match the wildcard: in a symbol""" + + self._state.errorRecovery = False + self.input.consume() + + + def mismatchIsUnwantedToken(self, input, ttype): + return input.LA(2) == ttype + + + def mismatchIsMissingToken(self, input, follow): + if follow is None: + # we have no information about the follow; we can only consume + # a single token and hope for the best + return False + + # compute what can follow this grammar element reference + if EOR_TOKEN_TYPE in follow: + if len(self._state.following) > 0: + # remove EOR if we're not the start symbol + follow = follow - set([EOR_TOKEN_TYPE]) + + viableTokensFollowingThisRule = self.computeContextSensitiveRuleFOLLOW() + follow = follow | viableTokensFollowingThisRule + + # if current token is consistent with what could come after set + # then we know we're missing a token; error recovery is free to + # "insert" the missing token + if input.LA(1) in follow or EOR_TOKEN_TYPE in follow: + return True + + return False + + + def mismatch(self, input, ttype, follow): + """ + Factor out what to do upon token mismatch so tree parsers can behave + differently. Override and call mismatchRecover(input, ttype, follow) + to get single token insertion and deletion. Use this to turn of + single token insertion and deletion. Override mismatchRecover + to call this instead. + """ + + if self.mismatchIsUnwantedToken(input, ttype): + raise UnwantedTokenException(ttype, input) + + elif self.mismatchIsMissingToken(input, follow): + raise MissingTokenException(ttype, input, None) + + raise MismatchedTokenException(ttype, input) + + +## def mismatchRecover(self, input, ttype, follow): +## if self.mismatchIsUnwantedToken(input, ttype): +## mte = UnwantedTokenException(ttype, input) + +## elif self.mismatchIsMissingToken(input, follow): +## mte = MissingTokenException(ttype, input) + +## else: +## mte = MismatchedTokenException(ttype, input) + +## self.recoverFromMismatchedToken(input, mte, ttype, follow) + + + def reportError(self, e): + """Report a recognition problem. + + This method sets errorRecovery to indicate the parser is recovering + not parsing. Once in recovery mode, no errors are generated. + To get out of recovery mode, the parser must successfully match + a token (after a resync). So it will go: + + 1. error occurs + 2. enter recovery mode, report error + 3. consume until token found in resynch set + 4. try to resume parsing + 5. next match() will reset errorRecovery mode + + If you override, make sure to update syntaxErrors if you care about + that. + + """ + + # if we've already reported an error and have not matched a token + # yet successfully, don't report any errors. + if self._state.errorRecovery: + return + + self._state.syntaxErrors += 1 # don't count spurious + self._state.errorRecovery = True + + self.displayRecognitionError(self.tokenNames, e) + + + def displayRecognitionError(self, tokenNames, e): + hdr = self.getErrorHeader(e) + msg = self.getErrorMessage(e, tokenNames) + self.emitErrorMessage(hdr+" "+msg) + + + def getErrorMessage(self, e, tokenNames): + """ + What error message should be generated for the various + exception types? + + Not very object-oriented code, but I like having all error message + generation within one method rather than spread among all of the + exception classes. This also makes it much easier for the exception + handling because the exception classes do not have to have pointers back + to this object to access utility routines and so on. Also, changing + the message for an exception type would be difficult because you + would have to subclassing exception, but then somehow get ANTLR + to make those kinds of exception objects instead of the default. + This looks weird, but trust me--it makes the most sense in terms + of flexibility. + + For grammar debugging, you will want to override this to add + more information such as the stack frame with + getRuleInvocationStack(e, this.getClass().getName()) and, + for no viable alts, the decision description and state etc... + + Override this to change the message generated for one or more + exception types. + """ + + if isinstance(e, UnwantedTokenException): + tokenName = "" + if e.expecting == EOF: + tokenName = "EOF" + + else: + tokenName = self.tokenNames[e.expecting] + + msg = "extraneous input %s expecting %s" % ( + self.getTokenErrorDisplay(e.getUnexpectedToken()), + tokenName + ) + + elif isinstance(e, MissingTokenException): + tokenName = "" + if e.expecting == EOF: + tokenName = "EOF" + + else: + tokenName = self.tokenNames[e.expecting] + + msg = "missing %s at %s" % ( + tokenName, self.getTokenErrorDisplay(e.token) + ) + + elif isinstance(e, MismatchedTokenException): + tokenName = "" + if e.expecting == EOF: + tokenName = "EOF" + else: + tokenName = self.tokenNames[e.expecting] + + msg = "mismatched input " \ + + self.getTokenErrorDisplay(e.token) \ + + " expecting " \ + + tokenName + + elif isinstance(e, MismatchedTreeNodeException): + tokenName = "" + if e.expecting == EOF: + tokenName = "EOF" + else: + tokenName = self.tokenNames[e.expecting] + + msg = "mismatched tree node: %s expecting %s" \ + % (e.node, tokenName) + + elif isinstance(e, NoViableAltException): + msg = "no viable alternative at input " \ + + self.getTokenErrorDisplay(e.token) + + elif isinstance(e, EarlyExitException): + msg = "required (...)+ loop did not match anything at input " \ + + self.getTokenErrorDisplay(e.token) + + elif isinstance(e, MismatchedSetException): + msg = "mismatched input " \ + + self.getTokenErrorDisplay(e.token) \ + + " expecting set " \ + + repr(e.expecting) + + elif isinstance(e, MismatchedNotSetException): + msg = "mismatched input " \ + + self.getTokenErrorDisplay(e.token) \ + + " expecting set " \ + + repr(e.expecting) + + elif isinstance(e, FailedPredicateException): + msg = "rule " \ + + e.ruleName \ + + " failed predicate: {" \ + + e.predicateText \ + + "}?" + + else: + msg = str(e) + + return msg + + + def getNumberOfSyntaxErrors(self): + """ + Get number of recognition errors (lexer, parser, tree parser). Each + recognizer tracks its own number. So parser and lexer each have + separate count. Does not count the spurious errors found between + an error and next valid token match + + See also reportError() + """ + return self._state.syntaxErrors + + + def getErrorHeader(self, e): + """ + What is the error header, normally line/character position information? + """ + + return "line %d:%d" % (e.line, e.charPositionInLine) + + + def getTokenErrorDisplay(self, t): + """ + How should a token be displayed in an error message? The default + is to display just the text, but during development you might + want to have a lot of information spit out. Override in that case + to use t.toString() (which, for CommonToken, dumps everything about + the token). This is better than forcing you to override a method in + your token objects because you don't have to go modify your lexer + so that it creates a new Java type. + """ + + s = t.text + if s is None: + if t.type == EOF: + s = "" + else: + s = "<"+t.type+">" + + return repr(s) + + + def emitErrorMessage(self, msg): + """Override this method to change where error messages go""" + sys.stderr.write(msg + '\n') + + + def recover(self, input, re): + """ + Recover from an error found on the input stream. This is + for NoViableAlt and mismatched symbol exceptions. If you enable + single token insertion and deletion, this will usually not + handle mismatched symbol exceptions but there could be a mismatched + token that the match() routine could not recover from. + """ + + # PROBLEM? what if input stream is not the same as last time + # perhaps make lastErrorIndex a member of input + if self._state.lastErrorIndex == input.index(): + # uh oh, another error at same token index; must be a case + # where LT(1) is in the recovery token set so nothing is + # consumed; consume a single token so at least to prevent + # an infinite loop; this is a failsafe. + input.consume() + + self._state.lastErrorIndex = input.index() + followSet = self.computeErrorRecoverySet() + + self.beginResync() + self.consumeUntil(input, followSet) + self.endResync() + + + def beginResync(self): + """ + A hook to listen in on the token consumption during error recovery. + The DebugParser subclasses this to fire events to the listenter. + """ + + pass + + + def endResync(self): + """ + A hook to listen in on the token consumption during error recovery. + The DebugParser subclasses this to fire events to the listenter. + """ + + pass + + + def computeErrorRecoverySet(self): + """ + Compute the error recovery set for the current rule. During + rule invocation, the parser pushes the set of tokens that can + follow that rule reference on the stack; this amounts to + computing FIRST of what follows the rule reference in the + enclosing rule. This local follow set only includes tokens + from within the rule; i.e., the FIRST computation done by + ANTLR stops at the end of a rule. + + EXAMPLE + + When you find a "no viable alt exception", the input is not + consistent with any of the alternatives for rule r. The best + thing to do is to consume tokens until you see something that + can legally follow a call to r *or* any rule that called r. + You don't want the exact set of viable next tokens because the + input might just be missing a token--you might consume the + rest of the input looking for one of the missing tokens. + + Consider grammar: + + a : '[' b ']' + | '(' b ')' + ; + b : c '^' INT ; + c : ID + | INT + ; + + At each rule invocation, the set of tokens that could follow + that rule is pushed on a stack. Here are the various "local" + follow sets: + + FOLLOW(b1_in_a) = FIRST(']') = ']' + FOLLOW(b2_in_a) = FIRST(')') = ')' + FOLLOW(c_in_b) = FIRST('^') = '^' + + Upon erroneous input "[]", the call chain is + + a -> b -> c + + and, hence, the follow context stack is: + + depth local follow set after call to rule + 0 \ a (from main()) + 1 ']' b + 3 '^' c + + Notice that ')' is not included, because b would have to have + been called from a different context in rule a for ')' to be + included. + + For error recovery, we cannot consider FOLLOW(c) + (context-sensitive or otherwise). We need the combined set of + all context-sensitive FOLLOW sets--the set of all tokens that + could follow any reference in the call chain. We need to + resync to one of those tokens. Note that FOLLOW(c)='^' and if + we resync'd to that token, we'd consume until EOF. We need to + sync to context-sensitive FOLLOWs for a, b, and c: {']','^'}. + In this case, for input "[]", LA(1) is in this set so we would + not consume anything and after printing an error rule c would + return normally. It would not find the required '^' though. + At this point, it gets a mismatched token error and throws an + exception (since LA(1) is not in the viable following token + set). The rule exception handler tries to recover, but finds + the same recovery set and doesn't consume anything. Rule b + exits normally returning to rule a. Now it finds the ']' (and + with the successful match exits errorRecovery mode). + + So, you cna see that the parser walks up call chain looking + for the token that was a member of the recovery set. + + Errors are not generated in errorRecovery mode. + + ANTLR's error recovery mechanism is based upon original ideas: + + "Algorithms + Data Structures = Programs" by Niklaus Wirth + + and + + "A note on error recovery in recursive descent parsers": + http://portal.acm.org/citation.cfm?id=947902.947905 + + Later, Josef Grosch had some good ideas: + + "Efficient and Comfortable Error Recovery in Recursive Descent + Parsers": + ftp://www.cocolab.com/products/cocktail/doca4.ps/ell.ps.zip + + Like Grosch I implemented local FOLLOW sets that are combined + at run-time upon error to avoid overhead during parsing. + """ + + return self.combineFollows(False) + + + def computeContextSensitiveRuleFOLLOW(self): + """ + Compute the context-sensitive FOLLOW set for current rule. + This is set of token types that can follow a specific rule + reference given a specific call chain. You get the set of + viable tokens that can possibly come next (lookahead depth 1) + given the current call chain. Contrast this with the + definition of plain FOLLOW for rule r: + + FOLLOW(r)={x | S=>*alpha r beta in G and x in FIRST(beta)} + + where x in T* and alpha, beta in V*; T is set of terminals and + V is the set of terminals and nonterminals. In other words, + FOLLOW(r) is the set of all tokens that can possibly follow + references to r in *any* sentential form (context). At + runtime, however, we know precisely which context applies as + we have the call chain. We may compute the exact (rather + than covering superset) set of following tokens. + + For example, consider grammar: + + stat : ID '=' expr ';' // FOLLOW(stat)=={EOF} + | "return" expr '.' + ; + expr : atom ('+' atom)* ; // FOLLOW(expr)=={';','.',')'} + atom : INT // FOLLOW(atom)=={'+',')',';','.'} + | '(' expr ')' + ; + + The FOLLOW sets are all inclusive whereas context-sensitive + FOLLOW sets are precisely what could follow a rule reference. + For input input "i=(3);", here is the derivation: + + stat => ID '=' expr ';' + => ID '=' atom ('+' atom)* ';' + => ID '=' '(' expr ')' ('+' atom)* ';' + => ID '=' '(' atom ')' ('+' atom)* ';' + => ID '=' '(' INT ')' ('+' atom)* ';' + => ID '=' '(' INT ')' ';' + + At the "3" token, you'd have a call chain of + + stat -> expr -> atom -> expr -> atom + + What can follow that specific nested ref to atom? Exactly ')' + as you can see by looking at the derivation of this specific + input. Contrast this with the FOLLOW(atom)={'+',')',';','.'}. + + You want the exact viable token set when recovering from a + token mismatch. Upon token mismatch, if LA(1) is member of + the viable next token set, then you know there is most likely + a missing token in the input stream. "Insert" one by just not + throwing an exception. + """ + + return self.combineFollows(True) + + + def combineFollows(self, exact): + followSet = set() + for idx, localFollowSet in reversed(list(enumerate(self._state.following))): + followSet |= localFollowSet + if exact: + # can we see end of rule? + if EOR_TOKEN_TYPE in localFollowSet: + # Only leave EOR in set if at top (start rule); this lets + # us know if have to include follow(start rule); i.e., EOF + if idx > 0: + followSet.remove(EOR_TOKEN_TYPE) + + else: + # can't see end of rule, quit + break + + return followSet + + + def recoverFromMismatchedToken(self, input, ttype, follow): + """Attempt to recover from a single missing or extra token. + + EXTRA TOKEN + + LA(1) is not what we are looking for. If LA(2) has the right token, + however, then assume LA(1) is some extra spurious token. Delete it + and LA(2) as if we were doing a normal match(), which advances the + input. + + MISSING TOKEN + + If current token is consistent with what could come after + ttype then it is ok to 'insert' the missing token, else throw + exception For example, Input 'i=(3;' is clearly missing the + ')'. When the parser returns from the nested call to expr, it + will have call chain: + + stat -> expr -> atom + + and it will be trying to match the ')' at this point in the + derivation: + + => ID '=' '(' INT ')' ('+' atom)* ';' + ^ + match() will see that ';' doesn't match ')' and report a + mismatched token error. To recover, it sees that LA(1)==';' + is in the set of tokens that can follow the ')' token + reference in rule atom. It can assume that you forgot the ')'. + """ + + e = None + + # if next token is what we are looking for then "delete" this token + if self. mismatchIsUnwantedToken(input, ttype): + e = UnwantedTokenException(ttype, input) + + self.beginResync() + input.consume() # simply delete extra token + self.endResync() + + # report after consuming so AW sees the token in the exception + self.reportError(e) + + # we want to return the token we're actually matching + matchedSymbol = self.getCurrentInputSymbol(input) + + # move past ttype token as if all were ok + input.consume() + return matchedSymbol + + # can't recover with single token deletion, try insertion + if self.mismatchIsMissingToken(input, follow): + inserted = self.getMissingSymbol(input, e, ttype, follow) + e = MissingTokenException(ttype, input, inserted) + + # report after inserting so AW sees the token in the exception + self.reportError(e) + return inserted + + # even that didn't work; must throw the exception + e = MismatchedTokenException(ttype, input) + raise e + + + def recoverFromMismatchedSet(self, input, e, follow): + """Not currently used""" + + if self.mismatchIsMissingToken(input, follow): + self.reportError(e) + # we don't know how to conjure up a token for sets yet + return self.getMissingSymbol(input, e, INVALID_TOKEN_TYPE, follow) + + # TODO do single token deletion like above for Token mismatch + raise e + + + def getCurrentInputSymbol(self, input): + """ + Match needs to return the current input symbol, which gets put + into the label for the associated token ref; e.g., x=ID. Token + and tree parsers need to return different objects. Rather than test + for input stream type or change the IntStream interface, I use + a simple method to ask the recognizer to tell me what the current + input symbol is. + + This is ignored for lexers. + """ + + return None + + + def getMissingSymbol(self, input, e, expectedTokenType, follow): + """Conjure up a missing token during error recovery. + + The recognizer attempts to recover from single missing + symbols. But, actions might refer to that missing symbol. + For example, x=ID {f($x);}. The action clearly assumes + that there has been an identifier matched previously and that + $x points at that token. If that token is missing, but + the next token in the stream is what we want we assume that + this token is missing and we keep going. Because we + have to return some token to replace the missing token, + we have to conjure one up. This method gives the user control + over the tokens returned for missing tokens. Mostly, + you will want to create something special for identifier + tokens. For literals such as '{' and ',', the default + action in the parser or tree parser works. It simply creates + a CommonToken of the appropriate type. The text will be the token. + If you change what tokens must be created by the lexer, + override this method to create the appropriate tokens. + """ + + return None + + +## def recoverFromMissingElement(self, input, e, follow): +## """ +## This code is factored out from mismatched token and mismatched set +## recovery. It handles "single token insertion" error recovery for +## both. No tokens are consumed to recover from insertions. Return +## true if recovery was possible else return false. +## """ + +## if self.mismatchIsMissingToken(input, follow): +## self.reportError(e) +## return True + +## # nothing to do; throw exception +## return False + + + def consumeUntil(self, input, tokenTypes): + """ + Consume tokens until one matches the given token or token set + + tokenTypes can be a single token type or a set of token types + + """ + + if not isinstance(tokenTypes, (set, frozenset)): + tokenTypes = frozenset([tokenTypes]) + + ttype = input.LA(1) + while ttype != EOF and ttype not in tokenTypes: + input.consume() + ttype = input.LA(1) + + + def getRuleInvocationStack(self): + """ + Return List of the rules in your parser instance + leading up to a call to this method. You could override if + you want more details such as the file/line info of where + in the parser java code a rule is invoked. + + This is very useful for error messages and for context-sensitive + error recovery. + + You must be careful, if you subclass a generated recognizers. + The default implementation will only search the module of self + for rules, but the subclass will not contain any rules. + You probably want to override this method to look like + + def getRuleInvocationStack(self): + return self._getRuleInvocationStack(.__module__) + + where is the class of the generated recognizer, e.g. + the superclass of self. + """ + + return self._getRuleInvocationStack(self.__module__) + + + def _getRuleInvocationStack(cls, module): + """ + A more general version of getRuleInvocationStack where you can + pass in, for example, a RecognitionException to get it's rule + stack trace. This routine is shared with all recognizers, hence, + static. + + TODO: move to a utility class or something; weird having lexer call + this + """ + + # mmmhhh,... perhaps look at the first argument + # (f_locals[co_varnames[0]]?) and test if it's a (sub)class of + # requested recognizer... + + rules = [] + for frame in reversed(inspect.stack()): + code = frame[0].f_code + codeMod = inspect.getmodule(code) + if codeMod is None: + continue + + # skip frames not in requested module + if codeMod.__name__ != module: + continue + + # skip some unwanted names + if code.co_name in ('nextToken', ''): + continue + + rules.append(code.co_name) + + return rules + + _getRuleInvocationStack = classmethod(_getRuleInvocationStack) + + + def getBacktrackingLevel(self): + return self._state.backtracking + + + def getGrammarFileName(self): + """For debugging and other purposes, might want the grammar name. + + Have ANTLR generate an implementation for this method. + """ + + return self.grammarFileName + + + def getSourceName(self): + raise NotImplementedError + + + def toStrings(self, tokens): + """A convenience method for use most often with template rewrites. + + Convert a List to List + """ + + if tokens is None: + return None + + return [token.text for token in tokens] + + + def getRuleMemoization(self, ruleIndex, ruleStartIndex): + """ + Given a rule number and a start token index number, return + MEMO_RULE_UNKNOWN if the rule has not parsed input starting from + start index. If this rule has parsed input starting from the + start index before, then return where the rule stopped parsing. + It returns the index of the last token matched by the rule. + """ + + if ruleIndex not in self._state.ruleMemo: + self._state.ruleMemo[ruleIndex] = {} + + return self._state.ruleMemo[ruleIndex].get( + ruleStartIndex, self.MEMO_RULE_UNKNOWN + ) + + + def alreadyParsedRule(self, input, ruleIndex): + """ + Has this rule already parsed input at the current index in the + input stream? Return the stop token index or MEMO_RULE_UNKNOWN. + If we attempted but failed to parse properly before, return + MEMO_RULE_FAILED. + + This method has a side-effect: if we have seen this input for + this rule and successfully parsed before, then seek ahead to + 1 past the stop token matched for this rule last time. + """ + + stopIndex = self.getRuleMemoization(ruleIndex, input.index()) + if stopIndex == self.MEMO_RULE_UNKNOWN: + return False + + if stopIndex == self.MEMO_RULE_FAILED: + raise BacktrackingFailed + + else: + input.seek(stopIndex + 1) + + return True + + + def memoize(self, input, ruleIndex, ruleStartIndex, success): + """ + Record whether or not this rule parsed the input at this position + successfully. + """ + + if success: + stopTokenIndex = input.index() - 1 + else: + stopTokenIndex = self.MEMO_RULE_FAILED + + if ruleIndex in self._state.ruleMemo: + self._state.ruleMemo[ruleIndex][ruleStartIndex] = stopTokenIndex + + + def traceIn(self, ruleName, ruleIndex, inputSymbol): + sys.stdout.write("enter %s %s" % (ruleName, inputSymbol)) + +## if self._state.failed: +## sys.stdout.write(" failed=%s" % self._state.failed) + + if self._state.backtracking > 0: + sys.stdout.write(" backtracking=%s" % self._state.backtracking) + + sys.stdout.write('\n') + + + def traceOut(self, ruleName, ruleIndex, inputSymbol): + sys.stdout.write("exit %s %s" % (ruleName, inputSymbol)) + +## if self._state.failed: +## sys.stdout.write(" failed=%s" % self._state.failed) + + if self._state.backtracking > 0: + sys.stdout.write(" backtracking=%s" % self._state.backtracking) + + sys.stdout.write('\n') + + + +class TokenSource(object): + """ + @brief Abstract baseclass for token producers. + + A source of tokens must provide a sequence of tokens via nextToken() + and also must reveal it's source of characters; CommonToken's text is + computed from a CharStream; it only store indices into the char stream. + + Errors from the lexer are never passed to the parser. Either you want + to keep going or you do not upon token recognition error. If you do not + want to continue lexing then you do not want to continue parsing. Just + throw an exception not under RecognitionException and Java will naturally + toss you all the way out of the recognizers. If you want to continue + lexing then you should not throw an exception to the parser--it has already + requested a token. Keep lexing until you get a valid one. Just report + errors and keep going, looking for a valid token. + """ + + def nextToken(self): + """Return a Token object from your input stream (usually a CharStream). + + Do not fail/return upon lexing error; keep chewing on the characters + until you get a good one; errors are not passed through to the parser. + """ + + raise NotImplementedError + + + def __iter__(self): + """The TokenSource is an interator. + + The iteration will not include the final EOF token, see also the note + for the next() method. + + """ + + return self + + + def next(self): + """Return next token or raise StopIteration. + + Note that this will raise StopIteration when hitting the EOF token, + so EOF will not be part of the iteration. + + """ + + token = self.nextToken() + if token is None or token.type == EOF: + raise StopIteration + return token + + +class Lexer(BaseRecognizer, TokenSource): + """ + @brief Baseclass for generated lexer classes. + + A lexer is recognizer that draws input symbols from a character stream. + lexer grammars result in a subclass of this object. A Lexer object + uses simplified match() and error recovery mechanisms in the interest + of speed. + """ + + def __init__(self, input, state=None): + BaseRecognizer.__init__(self, state) + TokenSource.__init__(self) + + # Where is the lexer drawing characters from? + self.input = input + + + def reset(self): + BaseRecognizer.reset(self) # reset all recognizer state variables + + if self.input is not None: + # rewind the input + self.input.seek(0) + + if self._state is None: + # no shared state work to do + return + + # wack Lexer state variables + self._state.token = None + self._state.type = INVALID_TOKEN_TYPE + self._state.channel = DEFAULT_CHANNEL + self._state.tokenStartCharIndex = -1 + self._state.tokenStartLine = -1 + self._state.tokenStartCharPositionInLine = -1 + self._state.text = None + + + def nextToken(self): + """ + Return a token from this source; i.e., match a token on the char + stream. + """ + + while 1: + self._state.token = None + self._state.channel = DEFAULT_CHANNEL + self._state.tokenStartCharIndex = self.input.index() + self._state.tokenStartCharPositionInLine = self.input.charPositionInLine + self._state.tokenStartLine = self.input.line + self._state.text = None + if self.input.LA(1) == EOF: + return EOF_TOKEN + + try: + self.mTokens() + + if self._state.token is None: + self.emit() + + elif self._state.token == SKIP_TOKEN: + continue + + return self._state.token + + except NoViableAltException, re: + self.reportError(re) + self.recover(re) # throw out current char and try again + + except RecognitionException, re: + self.reportError(re) + # match() routine has already called recover() + + + def skip(self): + """ + Instruct the lexer to skip creating a token for current lexer rule + and look for another token. nextToken() knows to keep looking when + a lexer rule finishes with token set to SKIP_TOKEN. Recall that + if token==null at end of any token rule, it creates one for you + and emits it. + """ + + self._state.token = SKIP_TOKEN + + + def mTokens(self): + """This is the lexer entry point that sets instance var 'token'""" + + # abstract method + raise NotImplementedError + + + def setCharStream(self, input): + """Set the char stream and reset the lexer""" + self.input = None + self.reset() + self.input = input + + + def getSourceName(self): + return self.input.getSourceName() + + + def emit(self, token=None): + """ + The standard method called to automatically emit a token at the + outermost lexical rule. The token object should point into the + char buffer start..stop. If there is a text override in 'text', + use that to set the token's text. Override this method to emit + custom Token objects. + + If you are building trees, then you should also override + Parser or TreeParser.getMissingSymbol(). + """ + + if token is None: + token = CommonToken( + input=self.input, + type=self._state.type, + channel=self._state.channel, + start=self._state.tokenStartCharIndex, + stop=self.getCharIndex()-1 + ) + token.line = self._state.tokenStartLine + token.text = self._state.text + token.charPositionInLine = self._state.tokenStartCharPositionInLine + + self._state.token = token + + return token + + + def match(self, s): + if isinstance(s, basestring): + for c in s: + if self.input.LA(1) != ord(c): + if self._state.backtracking > 0: + raise BacktrackingFailed + + mte = MismatchedTokenException(c, self.input) + self.recover(mte) + raise mte + + self.input.consume() + + else: + if self.input.LA(1) != s: + if self._state.backtracking > 0: + raise BacktrackingFailed + + mte = MismatchedTokenException(unichr(s), self.input) + self.recover(mte) # don't really recover; just consume in lexer + raise mte + + self.input.consume() + + + def matchAny(self): + self.input.consume() + + + def matchRange(self, a, b): + if self.input.LA(1) < a or self.input.LA(1) > b: + if self._state.backtracking > 0: + raise BacktrackingFailed + + mre = MismatchedRangeException(unichr(a), unichr(b), self.input) + self.recover(mre) + raise mre + + self.input.consume() + + + def getLine(self): + return self.input.line + + + def getCharPositionInLine(self): + return self.input.charPositionInLine + + + def getCharIndex(self): + """What is the index of the current character of lookahead?""" + + return self.input.index() + + + def getText(self): + """ + Return the text matched so far for the current token or any + text override. + """ + if self._state.text is not None: + return self._state.text + + return self.input.substring( + self._state.tokenStartCharIndex, + self.getCharIndex()-1 + ) + + + def setText(self, text): + """ + Set the complete text of this token; it wipes any previous + changes to the text. + """ + self._state.text = text + + + text = property(getText, setText) + + + def reportError(self, e): + ## TODO: not thought about recovery in lexer yet. + + ## # if we've already reported an error and have not matched a token + ## # yet successfully, don't report any errors. + ## if self.errorRecovery: + ## #System.err.print("[SPURIOUS] "); + ## return; + ## + ## self.errorRecovery = True + + self.displayRecognitionError(self.tokenNames, e) + + + def getErrorMessage(self, e, tokenNames): + msg = None + + if isinstance(e, MismatchedTokenException): + msg = "mismatched character " \ + + self.getCharErrorDisplay(e.c) \ + + " expecting " \ + + self.getCharErrorDisplay(e.expecting) + + elif isinstance(e, NoViableAltException): + msg = "no viable alternative at character " \ + + self.getCharErrorDisplay(e.c) + + elif isinstance(e, EarlyExitException): + msg = "required (...)+ loop did not match anything at character " \ + + self.getCharErrorDisplay(e.c) + + elif isinstance(e, MismatchedNotSetException): + msg = "mismatched character " \ + + self.getCharErrorDisplay(e.c) \ + + " expecting set " \ + + repr(e.expecting) + + elif isinstance(e, MismatchedSetException): + msg = "mismatched character " \ + + self.getCharErrorDisplay(e.c) \ + + " expecting set " \ + + repr(e.expecting) + + elif isinstance(e, MismatchedRangeException): + msg = "mismatched character " \ + + self.getCharErrorDisplay(e.c) \ + + " expecting set " \ + + self.getCharErrorDisplay(e.a) \ + + ".." \ + + self.getCharErrorDisplay(e.b) + + else: + msg = BaseRecognizer.getErrorMessage(self, e, tokenNames) + + return msg + + + def getCharErrorDisplay(self, c): + if c == EOF: + c = '' + return repr(c) + + + def recover(self, re): + """ + Lexers can normally match any char in it's vocabulary after matching + a token, so do the easy thing and just kill a character and hope + it all works out. You can instead use the rule invocation stack + to do sophisticated error recovery if you are in a fragment rule. + """ + + self.input.consume() + + + def traceIn(self, ruleName, ruleIndex): + inputSymbol = "%s line=%d:%s" % (self.input.LT(1), + self.getLine(), + self.getCharPositionInLine() + ) + + BaseRecognizer.traceIn(self, ruleName, ruleIndex, inputSymbol) + + + def traceOut(self, ruleName, ruleIndex): + inputSymbol = "%s line=%d:%s" % (self.input.LT(1), + self.getLine(), + self.getCharPositionInLine() + ) + + BaseRecognizer.traceOut(self, ruleName, ruleIndex, inputSymbol) + + + +class Parser(BaseRecognizer): + """ + @brief Baseclass for generated parser classes. + """ + + def __init__(self, lexer, state=None): + BaseRecognizer.__init__(self, state) + + self.setTokenStream(lexer) + + + def reset(self): + BaseRecognizer.reset(self) # reset all recognizer state variables + if self.input is not None: + self.input.seek(0) # rewind the input + + + def getCurrentInputSymbol(self, input): + return input.LT(1) + + + def getMissingSymbol(self, input, e, expectedTokenType, follow): + if expectedTokenType == EOF: + tokenText = "" + else: + tokenText = "" + t = CommonToken(type=expectedTokenType, text=tokenText) + current = input.LT(1) + if current.type == EOF: + current = input.LT(-1) + + if current is not None: + t.line = current.line + t.charPositionInLine = current.charPositionInLine + t.channel = DEFAULT_CHANNEL + return t + + + def setTokenStream(self, input): + """Set the token stream and reset the parser""" + + self.input = None + self.reset() + self.input = input + + + def getTokenStream(self): + return self.input + + + def getSourceName(self): + return self.input.getSourceName() + + + def traceIn(self, ruleName, ruleIndex): + BaseRecognizer.traceIn(self, ruleName, ruleIndex, self.input.LT(1)) + + + def traceOut(self, ruleName, ruleIndex): + BaseRecognizer.traceOut(self, ruleName, ruleIndex, self.input.LT(1)) + + +class RuleReturnScope(object): + """ + Rules can return start/stop info as well as possible trees and templates. + """ + + def getStart(self): + """Return the start token or tree.""" + return None + + + def getStop(self): + """Return the stop token or tree.""" + return None + + + def getTree(self): + """Has a value potentially if output=AST.""" + return None + + + def getTemplate(self): + """Has a value potentially if output=template.""" + return None + + +class ParserRuleReturnScope(RuleReturnScope): + """ + Rules that return more than a single value must return an object + containing all the values. Besides the properties defined in + RuleLabelScope.predefinedRulePropertiesScope there may be user-defined + return values. This class simply defines the minimum properties that + are always defined and methods to access the others that might be + available depending on output option such as template and tree. + + Note text is not an actual property of the return value, it is computed + from start and stop using the input stream's toString() method. I + could add a ctor to this so that we can pass in and store the input + stream, but I'm not sure we want to do that. It would seem to be undefined + to get the .text property anyway if the rule matches tokens from multiple + input streams. + + I do not use getters for fields of objects that are used simply to + group values such as this aggregate. The getters/setters are there to + satisfy the superclass interface. + """ + + def __init__(self): + self.start = None + self.stop = None + + + def getStart(self): + return self.start + + + def getStop(self): + return self.stop + diff --git a/google_appengine/lib/antlr3/antlr3/recognizers.pyc b/google_appengine/lib/antlr3/antlr3/recognizers.pyc new file mode 100644 index 0000000000000000000000000000000000000000..541e2da990eb94bdcc99d050e9d1c58bcf420add GIT binary patch literal 48358 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 Z$5)U4sltDM`S|Bf{i%BK_{+yn{a;_m+im~= literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/streams.py b/google_appengine/lib/antlr3/antlr3/streams.py new file mode 100755 index 0000000..0dbe0f1 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/streams.py @@ -0,0 +1,1452 @@ +"""ANTLR3 runtime package""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +import codecs +from StringIO import StringIO + +from antlr3.constants import DEFAULT_CHANNEL, EOF +from antlr3.tokens import Token, EOF_TOKEN + + +############################################################################ +# +# basic interfaces +# IntStream +# +- CharStream +# \- TokenStream +# +# subclasses must implemented all methods +# +############################################################################ + +class IntStream(object): + """ + @brief Base interface for streams of integer values. + + A simple stream of integers used when all I care about is the char + or token type sequence (such as interpretation). + """ + + def consume(self): + raise NotImplementedError + + + def LA(self, i): + """Get int at current input pointer + i ahead where i=1 is next int. + + Negative indexes are allowed. LA(-1) is previous token (token + just matched). LA(-i) where i is before first token should + yield -1, invalid char / EOF. + """ + + raise NotImplementedError + + + def mark(self): + """ + Tell the stream to start buffering if it hasn't already. Return + current input position, index(), or some other marker so that + when passed to rewind() you get back to the same spot. + rewind(mark()) should not affect the input cursor. The Lexer + track line/col info as well as input index so its markers are + not pure input indexes. Same for tree node streams. + """ + + raise NotImplementedError + + + def index(self): + """ + Return the current input symbol index 0..n where n indicates the + last symbol has been read. The index is the symbol about to be + read not the most recently read symbol. + """ + + raise NotImplementedError + + + def rewind(self, marker=None): + """ + Reset the stream so that next call to index would return marker. + The marker will usually be index() but it doesn't have to be. It's + just a marker to indicate what state the stream was in. This is + essentially calling release() and seek(). If there are markers + created after this marker argument, this routine must unroll them + like a stack. Assume the state the stream was in when this marker + was created. + + If marker is None: + Rewind to the input position of the last marker. + Used currently only after a cyclic DFA and just + before starting a sem/syn predicate to get the + input position back to the start of the decision. + Do not "pop" the marker off the state. mark(i) + and rewind(i) should balance still. It is + like invoking rewind(last marker) but it should not "pop" + the marker off. It's like seek(last marker's input position). + """ + + raise NotImplementedError + + + def release(self, marker=None): + """ + You may want to commit to a backtrack but don't want to force the + stream to keep bookkeeping objects around for a marker that is + no longer necessary. This will have the same behavior as + rewind() except it releases resources without the backward seek. + This must throw away resources for all markers back to the marker + argument. So if you're nested 5 levels of mark(), and then release(2) + you have to release resources for depths 2..5. + """ + + raise NotImplementedError + + + def seek(self, index): + """ + Set the input cursor to the position indicated by index. This is + normally used to seek ahead in the input stream. No buffering is + required to do this unless you know your stream will use seek to + move backwards such as when backtracking. + + This is different from rewind in its multi-directional + requirement and in that its argument is strictly an input cursor + (index). + + For char streams, seeking forward must update the stream state such + as line number. For seeking backwards, you will be presumably + backtracking using the mark/rewind mechanism that restores state and + so this method does not need to update state when seeking backwards. + + Currently, this method is only used for efficient backtracking using + memoization, but in the future it may be used for incremental parsing. + + The index is 0..n-1. A seek to position i means that LA(1) will + return the ith symbol. So, seeking to 0 means LA(1) will return the + first element in the stream. + """ + + raise NotImplementedError + + + def size(self): + """ + Only makes sense for streams that buffer everything up probably, but + might be useful to display the entire stream or for testing. This + value includes a single EOF. + """ + + raise NotImplementedError + + + def getSourceName(self): + """ + Where are you getting symbols from? Normally, implementations will + pass the buck all the way to the lexer who can ask its input stream + for the file name or whatever. + """ + + raise NotImplementedError + + +class CharStream(IntStream): + """ + @brief A source of characters for an ANTLR lexer. + + This is an abstract class that must be implemented by a subclass. + + """ + + # pylint does not realize that this is an interface, too + #pylint: disable-msg=W0223 + + EOF = -1 + + + def substring(self, start, stop): + """ + For infinite streams, you don't need this; primarily I'm providing + a useful interface for action code. Just make sure actions don't + use this on streams that don't support it. + """ + + raise NotImplementedError + + + def LT(self, i): + """ + Get the ith character of lookahead. This is the same usually as + LA(i). This will be used for labels in the generated + lexer code. I'd prefer to return a char here type-wise, but it's + probably better to be 32-bit clean and be consistent with LA. + """ + + raise NotImplementedError + + + def getLine(self): + """ANTLR tracks the line information automatically""" + + raise NotImplementedError + + + def setLine(self, line): + """ + Because this stream can rewind, we need to be able to reset the line + """ + + raise NotImplementedError + + + def getCharPositionInLine(self): + """ + The index of the character relative to the beginning of the line 0..n-1 + """ + + raise NotImplementedError + + + def setCharPositionInLine(self, pos): + raise NotImplementedError + + +class TokenStream(IntStream): + """ + + @brief A stream of tokens accessing tokens from a TokenSource + + This is an abstract class that must be implemented by a subclass. + + """ + + # pylint does not realize that this is an interface, too + #pylint: disable-msg=W0223 + + def LT(self, k): + """ + Get Token at current input pointer + i ahead where i=1 is next Token. + i<0 indicates tokens in the past. So -1 is previous token and -2 is + two tokens ago. LT(0) is undefined. For i>=n, return Token.EOFToken. + Return null for LT(0) and any index that results in an absolute address + that is negative. + """ + + raise NotImplementedError + + + def get(self, i): + """ + Get a token at an absolute index i; 0..n-1. This is really only + needed for profiling and debugging and token stream rewriting. + If you don't want to buffer up tokens, then this method makes no + sense for you. Naturally you can't use the rewrite stream feature. + I believe DebugTokenStream can easily be altered to not use + this method, removing the dependency. + """ + + raise NotImplementedError + + + def getTokenSource(self): + """ + Where is this stream pulling tokens from? This is not the name, but + the object that provides Token objects. + """ + + raise NotImplementedError + + + def toString(self, start=None, stop=None): + """ + Return the text of all tokens from start to stop, inclusive. + If the stream does not buffer all the tokens then it can just + return "" or null; Users should not access $ruleLabel.text in + an action of course in that case. + + Because the user is not required to use a token with an index stored + in it, we must provide a means for two token objects themselves to + indicate the start/end location. Most often this will just delegate + to the other toString(int,int). This is also parallel with + the TreeNodeStream.toString(Object,Object). + """ + + raise NotImplementedError + + +############################################################################ +# +# character streams for use in lexers +# CharStream +# \- ANTLRStringStream +# +############################################################################ + + +class ANTLRStringStream(CharStream): + """ + @brief CharStream that pull data from a unicode string. + + A pretty quick CharStream that pulls all data from an array + directly. Every method call counts in the lexer. + + """ + + + def __init__(self, data): + """ + @param data This should be a unicode string holding the data you want + to parse. If you pass in a byte string, the Lexer will choke on + non-ascii data. + + """ + + CharStream.__init__(self) + + # The data being scanned + self.strdata = unicode(data) + self.data = [ord(c) for c in self.strdata] + + # How many characters are actually in the buffer + self.n = len(data) + + # 0..n-1 index into string of next char + self.p = 0 + + # line number 1..n within the input + self.line = 1 + + # The index of the character relative to the beginning of the + # line 0..n-1 + self.charPositionInLine = 0 + + # A list of CharStreamState objects that tracks the stream state + # values line, charPositionInLine, and p that can change as you + # move through the input stream. Indexed from 0..markDepth-1. + self._markers = [ ] + self.lastMarker = None + self.markDepth = 0 + + # What is name or source of this char stream? + self.name = None + + + def reset(self): + """ + Reset the stream so that it's in the same state it was + when the object was created *except* the data array is not + touched. + """ + + self.p = 0 + self.line = 1 + self.charPositionInLine = 0 + self._markers = [ ] + + + def consume(self): + try: + if self.data[self.p] == 10: # \n + self.line += 1 + self.charPositionInLine = 0 + else: + self.charPositionInLine += 1 + + self.p += 1 + + except IndexError: + # happend when we reached EOF and self.data[self.p] fails + # just do nothing + pass + + + + def LA(self, i): + if i == 0: + return 0 # undefined + + if i < 0: + i += 1 # e.g., translate LA(-1) to use offset i=0; then data[p+0-1] + + try: + return self.data[self.p+i-1] + except IndexError: + return EOF + + + + def LT(self, i): + if i == 0: + return 0 # undefined + + if i < 0: + i += 1 # e.g., translate LA(-1) to use offset i=0; then data[p+0-1] + + try: + return self.strdata[self.p+i-1] + except IndexError: + return EOF + + + def index(self): + """ + Return the current input symbol index 0..n where n indicates the + last symbol has been read. The index is the index of char to + be returned from LA(1). + """ + + return self.p + + + def size(self): + return self.n + + + def mark(self): + state = (self.p, self.line, self.charPositionInLine) + try: + self._markers[self.markDepth] = state + except IndexError: + self._markers.append(state) + self.markDepth += 1 + + self.lastMarker = self.markDepth + + return self.lastMarker + + + def rewind(self, marker=None): + if marker is None: + marker = self.lastMarker + + p, line, charPositionInLine = self._markers[marker-1] + + self.seek(p) + self.line = line + self.charPositionInLine = charPositionInLine + self.release(marker) + + + def release(self, marker=None): + if marker is None: + marker = self.lastMarker + + self.markDepth = marker-1 + + + def seek(self, index): + """ + consume() ahead until p==index; can't just set p=index as we must + update line and charPositionInLine. + """ + + if index <= self.p: + self.p = index # just jump; don't update stream state (line, ...) + return + + # seek forward, consume until p hits index + while self.p < index: + self.consume() + + + def substring(self, start, stop): + return self.strdata[start:stop+1] + + + def getLine(self): + """Using setter/getter methods is deprecated. Use o.line instead.""" + return self.line + + + def getCharPositionInLine(self): + """ + Using setter/getter methods is deprecated. Use o.charPositionInLine + instead. + """ + return self.charPositionInLine + + + def setLine(self, line): + """Using setter/getter methods is deprecated. Use o.line instead.""" + self.line = line + + + def setCharPositionInLine(self, pos): + """ + Using setter/getter methods is deprecated. Use o.charPositionInLine + instead. + """ + self.charPositionInLine = pos + + + def getSourceName(self): + return self.name + + +class ANTLRFileStream(ANTLRStringStream): + """ + @brief CharStream that opens a file to read the data. + + This is a char buffer stream that is loaded from a file + all at once when you construct the object. + """ + + def __init__(self, fileName, encoding=None): + """ + @param fileName The path to the file to be opened. The file will be + opened with mode 'rb'. + + @param encoding If you set the optional encoding argument, then the + data will be decoded on the fly. + + """ + + self.fileName = fileName + + fp = codecs.open(fileName, 'rb', encoding) + try: + data = fp.read() + finally: + fp.close() + + ANTLRStringStream.__init__(self, data) + + + def getSourceName(self): + """Deprecated, access o.fileName directly.""" + + return self.fileName + + +class ANTLRInputStream(ANTLRStringStream): + """ + @brief CharStream that reads data from a file-like object. + + This is a char buffer stream that is loaded from a file like object + all at once when you construct the object. + + All input is consumed from the file, but it is not closed. + """ + + def __init__(self, file, encoding=None): + """ + @param file A file-like object holding your input. Only the read() + method must be implemented. + + @param encoding If you set the optional encoding argument, then the + data will be decoded on the fly. + + """ + + if encoding is not None: + # wrap input in a decoding reader + reader = codecs.lookup(encoding)[2] + file = reader(file) + + data = file.read() + + ANTLRStringStream.__init__(self, data) + + +# I guess the ANTLR prefix exists only to avoid a name clash with some Java +# mumbojumbo. A plain "StringStream" looks better to me, which should be +# the preferred name in Python. +StringStream = ANTLRStringStream +FileStream = ANTLRFileStream +InputStream = ANTLRInputStream + + +############################################################################ +# +# Token streams +# TokenStream +# +- CommonTokenStream +# \- TokenRewriteStream +# +############################################################################ + + +class CommonTokenStream(TokenStream): + """ + @brief The most common stream of tokens + + The most common stream of tokens is one where every token is buffered up + and tokens are prefiltered for a certain channel (the parser will only + see these tokens and cannot change the filter channel number during the + parse). + """ + + def __init__(self, tokenSource=None, channel=DEFAULT_CHANNEL): + """ + @param tokenSource A TokenSource instance (usually a Lexer) to pull + the tokens from. + + @param channel Skip tokens on any channel but this one; this is how we + skip whitespace... + + """ + + TokenStream.__init__(self) + + self.tokenSource = tokenSource + + # Record every single token pulled from the source so we can reproduce + # chunks of it later. + self.tokens = [] + + # Map to override some Tokens' channel numbers + self.channelOverrideMap = {} + + # Set; discard any tokens with this type + self.discardSet = set() + + # Skip tokens on any channel but this one; this is how we skip whitespace... + self.channel = channel + + # By default, track all incoming tokens + self.discardOffChannelTokens = False + + # The index into the tokens list of the current token (next token + # to consume). p==-1 indicates that the tokens list is empty + self.p = -1 + + # Remember last marked position + self.lastMarker = None + + + def setTokenSource(self, tokenSource): + """Reset this token stream by setting its token source.""" + + self.tokenSource = tokenSource + self.tokens = [] + self.p = -1 + self.channel = DEFAULT_CHANNEL + + + def reset(self): + self.p = 0 + self.lastMarker = None + + + def fillBuffer(self): + """ + Load all tokens from the token source and put in tokens. + This is done upon first LT request because you might want to + set some token type / channel overrides before filling buffer. + """ + + + index = 0 + t = self.tokenSource.nextToken() + while t is not None and t.type != EOF: + discard = False + + if self.discardSet is not None and t.type in self.discardSet: + discard = True + + elif self.discardOffChannelTokens and t.channel != self.channel: + discard = True + + # is there a channel override for token type? + try: + overrideChannel = self.channelOverrideMap[t.type] + + except KeyError: + # no override for this type + pass + + else: + if overrideChannel == self.channel: + t.channel = overrideChannel + else: + discard = True + + if not discard: + t.index = index + self.tokens.append(t) + index += 1 + + t = self.tokenSource.nextToken() + + # leave p pointing at first token on channel + self.p = 0 + self.p = self.skipOffTokenChannels(self.p) + + + def consume(self): + """ + Move the input pointer to the next incoming token. The stream + must become active with LT(1) available. consume() simply + moves the input pointer so that LT(1) points at the next + input symbol. Consume at least one token. + + Walk past any token not on the channel the parser is listening to. + """ + + if self.p < len(self.tokens): + self.p += 1 + + self.p = self.skipOffTokenChannels(self.p) # leave p on valid token + + + def skipOffTokenChannels(self, i): + """ + Given a starting index, return the index of the first on-channel + token. + """ + + try: + while self.tokens[i].channel != self.channel: + i += 1 + except IndexError: + # hit the end of token stream + pass + + return i + + + def skipOffTokenChannelsReverse(self, i): + while i >= 0 and self.tokens[i].channel != self.channel: + i -= 1 + + return i + + + def setTokenTypeChannel(self, ttype, channel): + """ + A simple filter mechanism whereby you can tell this token stream + to force all tokens of type ttype to be on channel. For example, + when interpreting, we cannot exec actions so we need to tell + the stream to force all WS and NEWLINE to be a different, ignored + channel. + """ + + self.channelOverrideMap[ttype] = channel + + + def discardTokenType(self, ttype): + self.discardSet.add(ttype) + + + def getTokens(self, start=None, stop=None, types=None): + """ + Given a start and stop index, return a list of all tokens in + the token type set. Return None if no tokens were found. This + method looks at both on and off channel tokens. + """ + + if self.p == -1: + self.fillBuffer() + + if stop is None or stop >= len(self.tokens): + stop = len(self.tokens) - 1 + + if start is None or stop < 0: + start = 0 + + if start > stop: + return None + + if isinstance(types, (int, long)): + # called with a single type, wrap into set + types = set([types]) + + filteredTokens = [ + token for token in self.tokens[start:stop] + if types is None or token.type in types + ] + + if len(filteredTokens) == 0: + return None + + return filteredTokens + + + def LT(self, k): + """ + Get the ith token from the current position 1..n where k=1 is the + first symbol of lookahead. + """ + + if self.p == -1: + self.fillBuffer() + + if k == 0: + return None + + if k < 0: + return self.LB(-k) + + i = self.p + n = 1 + # find k good tokens + while n < k: + # skip off-channel tokens + i = self.skipOffTokenChannels(i+1) # leave p on valid token + n += 1 + + try: + return self.tokens[i] + except IndexError: + return EOF_TOKEN + + + def LB(self, k): + """Look backwards k tokens on-channel tokens""" + + if self.p == -1: + self.fillBuffer() + + if k == 0: + return None + + if self.p - k < 0: + return None + + i = self.p + n = 1 + # find k good tokens looking backwards + while n <= k: + # skip off-channel tokens + i = self.skipOffTokenChannelsReverse(i-1) # leave p on valid token + n += 1 + + if i < 0: + return None + + return self.tokens[i] + + + def get(self, i): + """ + Return absolute token i; ignore which channel the tokens are on; + that is, count all tokens not just on-channel tokens. + """ + + return self.tokens[i] + + + def LA(self, i): + return self.LT(i).type + + + def mark(self): + self.lastMarker = self.index() + return self.lastMarker + + + def release(self, marker=None): + # no resources to release + pass + + + def size(self): + return len(self.tokens) + + + def index(self): + return self.p + + + def rewind(self, marker=None): + if marker is None: + marker = self.lastMarker + + self.seek(marker) + + + def seek(self, index): + self.p = index + + + def getTokenSource(self): + return self.tokenSource + + + def getSourceName(self): + return self.tokenSource.getSourceName() + + + def toString(self, start=None, stop=None): + if self.p == -1: + self.fillBuffer() + + if start is None: + start = 0 + elif not isinstance(start, int): + start = start.index + + if stop is None: + stop = len(self.tokens) - 1 + elif not isinstance(stop, int): + stop = stop.index + + if stop >= len(self.tokens): + stop = len(self.tokens) - 1 + + return ''.join([t.text for t in self.tokens[start:stop+1]]) + + +class RewriteOperation(object): + """@brief Internal helper class.""" + + def __init__(self, stream, index, text): + self.stream = stream + self.index = index + self.text = text + + def execute(self, buf): + """Execute the rewrite operation by possibly adding to the buffer. + Return the index of the next token to operate on. + """ + + return self.index + + def toString(self): + opName = self.__class__.__name__ + return '<%s@%d:"%s">' % (opName, self.index, self.text) + + __str__ = toString + __repr__ = toString + + +class InsertBeforeOp(RewriteOperation): + """@brief Internal helper class.""" + + def execute(self, buf): + buf.write(self.text) + buf.write(self.stream.tokens[self.index].text) + return self.index + 1 + + +class ReplaceOp(RewriteOperation): + """ + @brief Internal helper class. + + I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp + instructions. + """ + + def __init__(self, stream, first, last, text): + RewriteOperation.__init__(self, stream, first, text) + self.lastIndex = last + + + def execute(self, buf): + if self.text is not None: + buf.write(self.text) + + return self.lastIndex + 1 + + + def toString(self): + return '' % ( + self.index, self.lastIndex, self.text) + + __str__ = toString + __repr__ = toString + + +class DeleteOp(ReplaceOp): + """ + @brief Internal helper class. + """ + + def __init__(self, stream, first, last): + ReplaceOp.__init__(self, stream, first, last, None) + + + def toString(self): + return '' % (self.index, self.lastIndex) + + __str__ = toString + __repr__ = toString + + +class TokenRewriteStream(CommonTokenStream): + """@brief CommonTokenStream that can be modified. + + Useful for dumping out the input stream after doing some + augmentation or other manipulations. + + You can insert stuff, replace, and delete chunks. Note that the + operations are done lazily--only if you convert the buffer to a + String. This is very efficient because you are not moving data around + all the time. As the buffer of tokens is converted to strings, the + toString() method(s) check to see if there is an operation at the + current index. If so, the operation is done and then normal String + rendering continues on the buffer. This is like having multiple Turing + machine instruction streams (programs) operating on a single input tape. :) + + Since the operations are done lazily at toString-time, operations do not + screw up the token index values. That is, an insert operation at token + index i does not change the index values for tokens i+1..n-1. + + Because operations never actually alter the buffer, you may always get + the original token stream back without undoing anything. Since + the instructions are queued up, you can easily simulate transactions and + roll back any changes if there is an error just by removing instructions. + For example, + + CharStream input = new ANTLRFileStream("input"); + TLexer lex = new TLexer(input); + TokenRewriteStream tokens = new TokenRewriteStream(lex); + T parser = new T(tokens); + parser.startRule(); + + Then in the rules, you can execute + Token t,u; + ... + input.insertAfter(t, "text to put after t");} + input.insertAfter(u, "text after u");} + System.out.println(tokens.toString()); + + Actually, you have to cast the 'input' to a TokenRewriteStream. :( + + You can also have multiple "instruction streams" and get multiple + rewrites from a single pass over the input. Just name the instruction + streams and use that name again when printing the buffer. This could be + useful for generating a C file and also its header file--all from the + same buffer: + + tokens.insertAfter("pass1", t, "text to put after t");} + tokens.insertAfter("pass2", u, "text after u");} + System.out.println(tokens.toString("pass1")); + System.out.println(tokens.toString("pass2")); + + If you don't use named rewrite streams, a "default" stream is used as + the first example shows. + """ + + DEFAULT_PROGRAM_NAME = "default" + MIN_TOKEN_INDEX = 0 + + def __init__(self, tokenSource=None, channel=DEFAULT_CHANNEL): + CommonTokenStream.__init__(self, tokenSource, channel) + + # You may have multiple, named streams of rewrite operations. + # I'm calling these things "programs." + # Maps String (name) -> rewrite (List) + self.programs = {} + self.programs[self.DEFAULT_PROGRAM_NAME] = [] + + # Map String (program name) -> Integer index + self.lastRewriteTokenIndexes = {} + + + def rollback(self, *args): + """ + Rollback the instruction stream for a program so that + the indicated instruction (via instructionIndex) is no + longer in the stream. UNTESTED! + """ + + if len(args) == 2: + programName = args[0] + instructionIndex = args[1] + elif len(args) == 1: + programName = self.DEFAULT_PROGRAM_NAME + instructionIndex = args[0] + else: + raise TypeError("Invalid arguments") + + p = self.programs.get(programName, None) + if p is not None: + self.programs[programName] = ( + p[self.MIN_TOKEN_INDEX:instructionIndex]) + + + def deleteProgram(self, programName=DEFAULT_PROGRAM_NAME): + """Reset the program so that no instructions exist""" + + self.rollback(programName, self.MIN_TOKEN_INDEX) + + + def insertAfter(self, *args): + if len(args) == 2: + programName = self.DEFAULT_PROGRAM_NAME + index = args[0] + text = args[1] + + elif len(args) == 3: + programName = args[0] + index = args[1] + text = args[2] + + else: + raise TypeError("Invalid arguments") + + if isinstance(index, Token): + # index is a Token, grap the stream index from it + index = index.index + + # to insert after, just insert before next index (even if past end) + self.insertBefore(programName, index+1, text) + + + def insertBefore(self, *args): + if len(args) == 2: + programName = self.DEFAULT_PROGRAM_NAME + index = args[0] + text = args[1] + + elif len(args) == 3: + programName = args[0] + index = args[1] + text = args[2] + + else: + raise TypeError("Invalid arguments") + + if isinstance(index, Token): + # index is a Token, grap the stream index from it + index = index.index + + op = InsertBeforeOp(self, index, text) + rewrites = self.getProgram(programName) + rewrites.append(op) + + + def replace(self, *args): + if len(args) == 2: + programName = self.DEFAULT_PROGRAM_NAME + first = args[0] + last = args[0] + text = args[1] + + elif len(args) == 3: + programName = self.DEFAULT_PROGRAM_NAME + first = args[0] + last = args[1] + text = args[2] + + elif len(args) == 4: + programName = args[0] + first = args[1] + last = args[2] + text = args[3] + + else: + raise TypeError("Invalid arguments") + + if isinstance(first, Token): + # first is a Token, grap the stream index from it + first = first.index + + if isinstance(last, Token): + # last is a Token, grap the stream index from it + last = last.index + + if first > last or first < 0 or last < 0 or last >= len(self.tokens): + raise ValueError( + "replace: range invalid: "+first+".."+last+ + "(size="+len(self.tokens)+")") + + op = ReplaceOp(self, first, last, text) + rewrites = self.getProgram(programName) + rewrites.append(op) + + + def delete(self, *args): + self.replace(*(list(args) + [None])) + + + def getLastRewriteTokenIndex(self, programName=DEFAULT_PROGRAM_NAME): + return self.lastRewriteTokenIndexes.get(programName, -1) + + + def setLastRewriteTokenIndex(self, programName, i): + self.lastRewriteTokenIndexes[programName] = i + + + def getProgram(self, name): + p = self.programs.get(name, None) + if p is None: + p = self.initializeProgram(name) + + return p + + + def initializeProgram(self, name): + p = [] + self.programs[name] = p + return p + + + def toOriginalString(self, start=None, end=None): + if start is None: + start = self.MIN_TOKEN_INDEX + if end is None: + end = self.size() - 1 + + buf = StringIO() + i = start + while i >= self.MIN_TOKEN_INDEX and i <= end and i < len(self.tokens): + buf.write(self.get(i).text) + i += 1 + + return buf.getvalue() + + + def toString(self, *args): + if len(args) == 0: + programName = self.DEFAULT_PROGRAM_NAME + start = self.MIN_TOKEN_INDEX + end = self.size() - 1 + + elif len(args) == 1: + programName = args[0] + start = self.MIN_TOKEN_INDEX + end = self.size() - 1 + + elif len(args) == 2: + programName = self.DEFAULT_PROGRAM_NAME + start = args[0] + end = args[1] + + if start is None: + start = self.MIN_TOKEN_INDEX + elif not isinstance(start, int): + start = start.index + + if end is None: + end = len(self.tokens) - 1 + elif not isinstance(end, int): + end = end.index + + # ensure start/end are in range + if end >= len(self.tokens): + end = len(self.tokens) - 1 + + if start < 0: + start = 0 + + rewrites = self.programs.get(programName) + if rewrites is None or len(rewrites) == 0: + # no instructions to execute + return self.toOriginalString(start, end) + + buf = StringIO() + + # First, optimize instruction stream + indexToOp = self.reduceToSingleOperationPerIndex(rewrites) + + # Walk buffer, executing instructions and emitting tokens + i = start + while i <= end and i < len(self.tokens): + op = indexToOp.get(i) + # remove so any left have index size-1 + try: + del indexToOp[i] + except KeyError: + pass + + t = self.tokens[i] + if op is None: + # no operation at that index, just dump token + buf.write(t.text) + i += 1 # move to next token + + else: + i = op.execute(buf) # execute operation and skip + + # include stuff after end if it's last index in buffer + # So, if they did an insertAfter(lastValidIndex, "foo"), include + # foo if end==lastValidIndex. + if end == len(self.tokens) - 1: + # Scan any remaining operations after last token + # should be included (they will be inserts). + for i in sorted(indexToOp.keys()): + op = indexToOp[i] + if op.index >= len(self.tokens)-1: + buf.write(op.text) + + return buf.getvalue() + + __str__ = toString + + + def reduceToSingleOperationPerIndex(self, rewrites): + """ + We need to combine operations and report invalid operations (like + overlapping replaces that are not completed nested). Inserts to + same index need to be combined etc... Here are the cases: + + I.i.u I.j.v leave alone, nonoverlapping + I.i.u I.i.v combine: Iivu + + R.i-j.u R.x-y.v | i-j in x-y delete first R + R.i-j.u R.i-j.v delete first R + R.i-j.u R.x-y.v | x-y in i-j ERROR + R.i-j.u R.x-y.v | boundaries overlap ERROR + + I.i.u R.x-y.v | i in x-y delete I + I.i.u R.x-y.v | i not in x-y leave alone, nonoverlapping + R.x-y.v I.i.u | i in x-y ERROR + R.x-y.v I.x.u R.x-y.uv (combine, delete I) + R.x-y.v I.i.u | i not in x-y leave alone, nonoverlapping + + I.i.u = insert u before op @ index i + R.x-y.u = replace x-y indexed tokens with u + + First we need to examine replaces. For any replace op: + + 1. wipe out any insertions before op within that range. + 2. Drop any replace op before that is contained completely within + that range. + 3. Throw exception upon boundary overlap with any previous replace. + + Then we can deal with inserts: + + 1. for any inserts to same index, combine even if not adjacent. + 2. for any prior replace with same left boundary, combine this + insert with replace and delete this replace. + 3. throw exception if index in same range as previous replace + + Don't actually delete; make op null in list. Easier to walk list. + Later we can throw as we add to index -> op map. + + Note that I.2 R.2-2 will wipe out I.2 even though, technically, the + inserted stuff would be before the replace range. But, if you + add tokens in front of a method body '{' and then delete the method + body, I think the stuff before the '{' you added should disappear too. + + Return a map from token index to operation. + """ + + # WALK REPLACES + for i, rop in enumerate(rewrites): + if rop is None: + continue + + if not isinstance(rop, ReplaceOp): + continue + + # Wipe prior inserts within range + for j, iop in self.getKindOfOps(rewrites, InsertBeforeOp, i): + if iop.index >= rop.index and iop.index <= rop.lastIndex: + rewrites[j] = None # delete insert as it's a no-op. + + # Drop any prior replaces contained within + for j, prevRop in self.getKindOfOps(rewrites, ReplaceOp, i): + if (prevRop.index >= rop.index + and prevRop.lastIndex <= rop.lastIndex): + rewrites[j] = None # delete replace as it's a no-op. + continue + + # throw exception unless disjoint or identical + disjoint = (prevRop.lastIndex < rop.index + or prevRop.index > rop.lastIndex) + same = (prevRop.index == rop.index + and prevRop.lastIndex == rop.lastIndex) + if not disjoint and not same: + raise ValueError( + "replace op boundaries of %s overlap with previous %s" + % (rop, prevRop)) + + # WALK INSERTS + for i, iop in enumerate(rewrites): + if iop is None: + continue + + if not isinstance(iop, InsertBeforeOp): + continue + + # combine current insert with prior if any at same index + for j, prevIop in self.getKindOfOps(rewrites, InsertBeforeOp, i): + if prevIop.index == iop.index: # combine objects + # convert to strings...we're in process of toString'ing + # whole token buffer so no lazy eval issue with any + # templates + iop.text = self.catOpText(iop.text, prevIop.text) + rewrites[j] = None # delete redundant prior insert + + # look for replaces where iop.index is in range; error + for j, rop in self.getKindOfOps(rewrites, ReplaceOp, i): + if iop.index == rop.index: + rop.text = self.catOpText(iop.text, rop.text) + rewrites[i] = None # delete current insert + continue + + if iop.index >= rop.index and iop.index <= rop.lastIndex: + raise ValueError( + "insert op %s within boundaries of previous %s" + % (iop, rop)) + + m = {} + for i, op in enumerate(rewrites): + if op is None: + continue # ignore deleted ops + + assert op.index not in m, "should only be one op per index" + m[op.index] = op + + return m + + + def catOpText(self, a, b): + x = "" + y = "" + if a is not None: + x = a + if b is not None: + y = b + return x + y + + + def getKindOfOps(self, rewrites, kind, before=None): + if before is None: + before = len(rewrites) + elif before > len(rewrites): + before = len(rewrites) + + for i, op in enumerate(rewrites[:before]): + if op is None: + # ignore deleted + continue + if op.__class__ == kind: + yield i, op + + + def toDebugString(self, start=None, end=None): + if start is None: + start = self.MIN_TOKEN_INDEX + if end is None: + end = self.size() - 1 + + buf = StringIO() + i = start + while i >= self.MIN_TOKEN_INDEX and i <= end and i < len(self.tokens): + buf.write(self.get(i)) + i += 1 + + return buf.getvalue() diff --git a/google_appengine/lib/antlr3/antlr3/streams.pyc b/google_appengine/lib/antlr3/antlr3/streams.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de8b8de30f13bdc34670a8909ef0ade3ea033ded GIT binary patch literal 44854 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 W5^}dRIr|-TarW43dG_(y!~Y+3A!ZE# literal 0 HcwPel00001 diff --git a/google_appengine/lib/antlr3/antlr3/tokens.py b/google_appengine/lib/antlr3/antlr3/tokens.py new file mode 100755 index 0000000..8ce835d --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/tokens.py @@ -0,0 +1,416 @@ +"""ANTLR3 runtime package""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +from antlr3.constants import EOF, DEFAULT_CHANNEL, INVALID_TOKEN_TYPE + +############################################################################ +# +# basic token interface +# +############################################################################ + +class Token(object): + """@brief Abstract token baseclass.""" + + def getText(self): + """@brief Get the text of the token. + + Using setter/getter methods is deprecated. Use o.text instead. + """ + raise NotImplementedError + + def setText(self, text): + """@brief Set the text of the token. + + Using setter/getter methods is deprecated. Use o.text instead. + """ + raise NotImplementedError + + + def getType(self): + """@brief Get the type of the token. + + Using setter/getter methods is deprecated. Use o.type instead.""" + + raise NotImplementedError + + def setType(self, ttype): + """@brief Get the type of the token. + + Using setter/getter methods is deprecated. Use o.type instead.""" + + raise NotImplementedError + + + def getLine(self): + """@brief Get the line number on which this token was matched + + Lines are numbered 1..n + + Using setter/getter methods is deprecated. Use o.line instead.""" + + raise NotImplementedError + + def setLine(self, line): + """@brief Set the line number on which this token was matched + + Using setter/getter methods is deprecated. Use o.line instead.""" + + raise NotImplementedError + + + def getCharPositionInLine(self): + """@brief Get the column of the tokens first character, + + Columns are numbered 0..n-1 + + Using setter/getter methods is deprecated. Use o.charPositionInLine instead.""" + + raise NotImplementedError + + def setCharPositionInLine(self, pos): + """@brief Set the column of the tokens first character, + + Using setter/getter methods is deprecated. Use o.charPositionInLine instead.""" + + raise NotImplementedError + + + def getChannel(self): + """@brief Get the channel of the token + + Using setter/getter methods is deprecated. Use o.channel instead.""" + + raise NotImplementedError + + def setChannel(self, channel): + """@brief Set the channel of the token + + Using setter/getter methods is deprecated. Use o.channel instead.""" + + raise NotImplementedError + + + def getTokenIndex(self): + """@brief Get the index in the input stream. + + An index from 0..n-1 of the token object in the input stream. + This must be valid in order to use the ANTLRWorks debugger. + + Using setter/getter methods is deprecated. Use o.index instead.""" + + raise NotImplementedError + + def setTokenIndex(self, index): + """@brief Set the index in the input stream. + + Using setter/getter methods is deprecated. Use o.index instead.""" + + raise NotImplementedError + + + def getInputStream(self): + """@brief From what character stream was this token created. + + You don't have to implement but it's nice to know where a Token + comes from if you have include files etc... on the input.""" + + raise NotImplementedError + + def setInputStream(self, input): + """@brief From what character stream was this token created. + + You don't have to implement but it's nice to know where a Token + comes from if you have include files etc... on the input.""" + + raise NotImplementedError + + +############################################################################ +# +# token implementations +# +# Token +# +- CommonToken +# \- ClassicToken +# +############################################################################ + +class CommonToken(Token): + """@brief Basic token implementation. + + This implementation does not copy the text from the input stream upon + creation, but keeps start/stop pointers into the stream to avoid + unnecessary copy operations. + + """ + + def __init__(self, type=None, channel=DEFAULT_CHANNEL, text=None, + input=None, start=None, stop=None, oldToken=None): + Token.__init__(self) + + if oldToken is not None: + self.type = oldToken.type + self.line = oldToken.line + self.charPositionInLine = oldToken.charPositionInLine + self.channel = oldToken.channel + self.index = oldToken.index + self._text = oldToken._text + if isinstance(oldToken, CommonToken): + self.input = oldToken.input + self.start = oldToken.start + self.stop = oldToken.stop + + else: + self.type = type + self.input = input + self.charPositionInLine = -1 # set to invalid position + self.line = 0 + self.channel = channel + + #What token number is this from 0..n-1 tokens; < 0 implies invalid index + self.index = -1 + + # We need to be able to change the text once in a while. If + # this is non-null, then getText should return this. Note that + # start/stop are not affected by changing this. + self._text = text + + # The char position into the input buffer where this token starts + self.start = start + + # The char position into the input buffer where this token stops + # This is the index of the last char, *not* the index after it! + self.stop = stop + + + def getText(self): + if self._text is not None: + return self._text + + if self.input is None: + return None + + return self.input.substring(self.start, self.stop) + + + def setText(self, text): + """ + Override the text for this token. getText() will return this text + rather than pulling from the buffer. Note that this does not mean + that start/stop indexes are not valid. It means that that input + was converted to a new string in the token object. + """ + self._text = text + + text = property(getText, setText) + + + def getType(self): + return self.type + + def setType(self, ttype): + self.type = ttype + + + def getLine(self): + return self.line + + def setLine(self, line): + self.line = line + + + def getCharPositionInLine(self): + return self.charPositionInLine + + def setCharPositionInLine(self, pos): + self.charPositionInLine = pos + + + def getChannel(self): + return self.channel + + def setChannel(self, channel): + self.channel = channel + + + def getTokenIndex(self): + return self.index + + def setTokenIndex(self, index): + self.index = index + + + def getInputStream(self): + return self.input + + def setInputStream(self, input): + self.input = input + + + def __str__(self): + if self.type == EOF: + return "" + + channelStr = "" + if self.channel > 0: + channelStr = ",channel=" + str(self.channel) + + txt = self.text + if txt is not None: + txt = txt.replace("\n","\\\\n") + txt = txt.replace("\r","\\\\r") + txt = txt.replace("\t","\\\\t") + else: + txt = "" + + return "[@%d,%d:%d=%r,<%d>%s,%d:%d]" % ( + self.index, + self.start, self.stop, + txt, + self.type, channelStr, + self.line, self.charPositionInLine + ) + + +class ClassicToken(Token): + """@brief Alternative token implementation. + + A Token object like we'd use in ANTLR 2.x; has an actual string created + and associated with this object. These objects are needed for imaginary + tree nodes that have payload objects. We need to create a Token object + that has a string; the tree node will point at this token. CommonToken + has indexes into a char stream and hence cannot be used to introduce + new strings. + """ + + def __init__(self, type=None, text=None, channel=DEFAULT_CHANNEL, + oldToken=None + ): + Token.__init__(self) + + if oldToken is not None: + self.text = oldToken.text + self.type = oldToken.type + self.line = oldToken.line + self.charPositionInLine = oldToken.charPositionInLine + self.channel = oldToken.channel + + self.text = text + self.type = type + self.line = None + self.charPositionInLine = None + self.channel = channel + self.index = None + + + def getText(self): + return self.text + + def setText(self, text): + self.text = text + + + def getType(self): + return self.type + + def setType(self, ttype): + self.type = ttype + + + def getLine(self): + return self.line + + def setLine(self, line): + self.line = line + + + def getCharPositionInLine(self): + return self.charPositionInLine + + def setCharPositionInLine(self, pos): + self.charPositionInLine = pos + + + def getChannel(self): + return self.channel + + def setChannel(self, channel): + self.channel = channel + + + def getTokenIndex(self): + return self.index + + def setTokenIndex(self, index): + self.index = index + + + def getInputStream(self): + return None + + def setInputStream(self, input): + pass + + + def toString(self): + channelStr = "" + if self.channel > 0: + channelStr = ",channel=" + str(self.channel) + + txt = self.text + if txt is None: + txt = "" + + return "[@%r,%r,<%r>%s,%r:%r]" % (self.index, + txt, + self.type, + channelStr, + self.line, + self.charPositionInLine + ) + + + __str__ = toString + __repr__ = toString + + + +EOF_TOKEN = CommonToken(type=EOF) + +INVALID_TOKEN = CommonToken(type=INVALID_TOKEN_TYPE) + +# In an action, a lexer rule can set token to this SKIP_TOKEN and ANTLR +# will avoid creating a token for this symbol and try to fetch another. +SKIP_TOKEN = CommonToken(type=INVALID_TOKEN_TYPE) + + diff --git a/google_appengine/lib/antlr3/antlr3/tokens.pyc b/google_appengine/lib/antlr3/antlr3/tokens.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36d06d15bae4f288f88eafcc762cca201b316647 GIT binary patch literal 13956 zcwXIG&2J<}6|e639@}g0N4yCvPysnRLC!c!Zpmg9@5T!ztJzhLLlh@r)b1{Odbg)* z=;~R=kO+}12N2xge?a2Ig&RUboVam=D}Mk85E6$-;l0;Y-R+OS?8NOztar-Q)!naO z{pwZK`~7^RM{iOK4k38?K<;oOw_5hp0zfZ-!QGsthLDmoMgUi;qMyl)mDD;=UNi z!gCYhci~dOWtWTvkxGQ?cQZ4+4my=wgrX;t&Cxg%BN4%E8*wb-^dttQ65(zK$Ab!m zBeFJ>Be52Fu`AbJmHSaBU0<#3Nx2t_fjb_HXfKGwS{UrCxlt0vFPLA{71bRdBn>bF zzHfnRt7sO1U^Nea*^hx&|E%$H5E633sTqXu`f?CbDMBw45Sl8Y0(Ly24#wj25JF=H zp&~Gr0j3;0Fk;}*FBR~ZDq=n}Jn}%H0Y)vw+N0k@Cyq$b|4b9!*9&&}$j- z{bAq@;RJL%UEcOx#Yb-94TYaWb_Z@!+>Onh!spkz-6%UhJGSJ`XA^7Ksc&Y8Hl~ZU z57@EQX(mKZ52(Yd6<$^jVk$+fpCLA74E~lKi$PC@lTmbNek#5j#46$5(8ax0#H+`H z>lWR1Bo3d4IDGEfDMTVZ`JPlk63A#Xy7L*t}(oo`1G)V%T(c2z@>lo zxa1*(%aXpb9E?NZB{ z{?ILKA{rq|dkR^Q2aO0q4&0k^!hIQCP597#h|$dhLsR(Tyl~)_5cWUMvG1J6r#GP(3?g?V1_N632ZNFHCm|j-=+KwmVBplz zU%c39VS#d)Gmiy6!$Hx=L3<7=V9q5hFcEPsWAP*wpJT=7I?Zl6m`gp2_P&Vyvg&rL z(Q4ACr9W72!>5^`8X*0y^GnQo*}ep9zFxJ1tK|;FxlkzAlITO^pEZdd@`2l z9HE&9x3AKydLYDD!8LB2tf@qf`B(-KOj~$Z1oI5dXajKMK9qq^MkbJUctR;RK1iP~ z$08=PN;~W|^|0aa)7tS*Mr%i4vG*AJ&|=nm4IIS#EhZn)K~TXn11OHLBOB zUZ;AU>J6$lsNSS{lO-)Ygt-C(EIX;)IXX8-*UeLXp6Ux!Utr()8?zz@uC0G8+pRP>E=9TE}jONERJn{t

nF3IMSCudu9se`ViA~;2(TE2}8^>vvp@@vbzh(I_Eu^;KSS`wUDh> zBjM)zHJvXkK$KY;K@Tn_h5*Jk)BBV$=qBj`ml?tg!;=wo5)unqu6QK&c{*GsPcG7w zZo8%Td>rU}&!>rS`eTVx=`W0BV#%>|79XpFR!1Q@hZD5S7{jpsYvz~rzhdho?{etc zrMQS`!QTrmP757+UM6&yV*QKF3LSZuL$^?hi@4kTn>{{sUuNY(hdJp-Y*y&VyBxZu zQe4E0`D6C@(BWoUCUlq*RIFK{BkywPE|%gVQjEIw_|RcSQzmrCdfGEXN8aVoEtldV zay@-Bz&sZv}-5_{aJ1O^F-WO^D~8pt3I zwX~2yrY%te9y5nEpO{I?X;L;5v^3iY46s3)$~m^%WXt^yBHmL7==F`ew{IlaYvLFR zd%UXE=a&`Y&{l}V+iyp7w4je-I%?^ogzmc@NlNx^sL#XETd%D6t1JGCEB?zX@#^&z z|Hg_+kKgH#!tgW}_)uC(=*6!!5}Y!5=1f~&x|ZW@so;Vcg!a6j z#AGii>q`!U0k}FCJc%)bZeWI8v+Gb^wp-SMbpd{s?YdpD>Oe#)KtXyP(o7H;=L!~I zF#U8W&XLjNY7Eh*ag5|RIY=S-6#9(QWyM)@nrIMN`~}*>aj7@z(ETT#X|-Fe#R@X6 zlRgP{;1+(_DDV!eH8_swm-;n3rq<+wjl~{SPZnvkD$_KoOv|V;ZKKM}8C7Q9s4@#i zl{sfrnMI?@EE!eiyisK?7**z?QDrVMaS25lEOUkCH&&+5tcQt+BR2^iicipKkb_XM zo+^^&Wk?u25PV-;_370H@DO^l!N1mh_a#0A&H@z5^^%Dj8mgkTQL^PmJ_m%Q7hn^x zvP8eW*AxboA%V(09ctDjgb&vuxeG=v480p4kg+5d$fmf38*%=MoR?Weht)5y+V zWo0ZB=+<>yFL6a;m7-*`WM@Ln^l1xm?Gn=#T#N6`oVJj6dD>E*FwmbNH8XT)CJa3MZ_riQanCp_7dIp)-#r1hPjZSJ$dM{f2YCMoQkYXU zj8pi9Dc~QP@-w6$hVoZX8c17@P=32W`I}6(NgqH859fVK4g>Rg5Oc-U3MGn=BJS3a zuwydPbMR&ONi_X1`NoI@@*S@d?T(3d$1kqL@1*bUD8<9qW;xSN+4a+mok*-5{9^~_ zIaUJTL~|3_r_B2Y2t0-bvvMNch7w6MejblPe=5R!4GUz=`c-Qz#z6P;{+}iY&hudA z8g?G7)>}xS=P!N|d8W^Wx<$j)OO+JFA;BFhHQn_jy~<5gDz6YY7|{uWlTxviD$tu= zd_29P+_-x?{p%+BHr1iOcOr46|Jvrg^spQt2 3.0""" + + raise NotImplementedError + + def setParent(self, t): + """Tree tracks parent and child index now > 3.0""" + + raise NotImplementedError + + + def getChildIndex(self): + """This node is what child index? 0..n-1""" + + raise NotImplementedError + + def setChildIndex(self, index): + """This node is what child index? 0..n-1""" + + raise NotImplementedError + + + def freshenParentAndChildIndexes(self): + """Set the parent and child index values for all children""" + + raise NotImplementedError + + + def addChild(self, t): + """ + Add t as a child to this node. If t is null, do nothing. If t + is nil, add all children of t to this' children. + """ + + raise NotImplementedError + + + def setChild(self, i, t): + """Set ith child (0..n-1) to t; t must be non-null and non-nil node""" + + raise NotImplementedError + + + def deleteChild(self, i): + raise NotImplementedError + + + def replaceChildren(self, startChildIndex, stopChildIndex, t): + """ + Delete children from start to stop and replace with t even if t is + a list (nil-root tree). num of children can increase or decrease. + For huge child lists, inserting children can force walking rest of + children to set their childindex; could be slow. + """ + + raise NotImplementedError + + + def isNil(self): + """ + Indicates the node is a nil node but may still have children, meaning + the tree is a flat list. + """ + + raise NotImplementedError + + + def getTokenStartIndex(self): + """ + What is the smallest token index (indexing from 0) for this node + and its children? + """ + + raise NotImplementedError + + + def setTokenStartIndex(self, index): + raise NotImplementedError + + + def getTokenStopIndex(self): + """ + What is the largest token index (indexing from 0) for this node + and its children? + """ + + raise NotImplementedError + + + def setTokenStopIndex(self, index): + raise NotImplementedError + + + def dupNode(self): + raise NotImplementedError + + + def getType(self): + """Return a token type; needed for tree parsing.""" + + raise NotImplementedError + + + def getText(self): + raise NotImplementedError + + + def getLine(self): + """ + In case we don't have a token payload, what is the line for errors? + """ + + raise NotImplementedError + + + def getCharPositionInLine(self): + raise NotImplementedError + + + def toStringTree(self): + raise NotImplementedError + + + def toString(self): + raise NotImplementedError + + + +class TreeAdaptor(object): + """ + @brief Abstract baseclass for tree adaptors. + + How to create and navigate trees. Rather than have a separate factory + and adaptor, I've merged them. Makes sense to encapsulate. + + This takes the place of the tree construction code generated in the + generated code in 2.x and the ASTFactory. + + I do not need to know the type of a tree at all so they are all + generic Objects. This may increase the amount of typecasting needed. :( + """ + + # C o n s t r u c t i o n + + def createWithPayload(self, payload): + """ + Create a tree node from Token object; for CommonTree type trees, + then the token just becomes the payload. This is the most + common create call. + + Override if you want another kind of node to be built. + """ + + raise NotImplementedError + + + def dupNode(self, treeNode): + """Duplicate a single tree node. + + Override if you want another kind of node to be built.""" + + raise NotImplementedError + + + def dupTree(self, tree): + """Duplicate tree recursively, using dupNode() for each node""" + + raise NotImplementedError + + + def nil(self): + """ + Return a nil node (an empty but non-null node) that can hold + a list of element as the children. If you want a flat tree (a list) + use "t=adaptor.nil(); t.addChild(x); t.addChild(y);" + """ + + raise NotImplementedError + + + def errorNode(self, input, start, stop, exc): + """ + Return a tree node representing an error. This node records the + tokens consumed during error recovery. The start token indicates the + input symbol at which the error was detected. The stop token indicates + the last symbol consumed during recovery. + + You must specify the input stream so that the erroneous text can + be packaged up in the error node. The exception could be useful + to some applications; default implementation stores ptr to it in + the CommonErrorNode. + + This only makes sense during token parsing, not tree parsing. + Tree parsing should happen only when parsing and tree construction + succeed. + """ + + raise NotImplementedError + + + def isNil(self, tree): + """Is tree considered a nil node used to make lists of child nodes?""" + + raise NotImplementedError + + + def addChild(self, t, child): + """ + Add a child to the tree t. If child is a flat tree (a list), make all + in list children of t. Warning: if t has no children, but child does + and child isNil then you can decide it is ok to move children to t via + t.children = child.children; i.e., without copying the array. Just + make sure that this is consistent with have the user will build + ASTs. Do nothing if t or child is null. + """ + + raise NotImplementedError + + + def becomeRoot(self, newRoot, oldRoot): + """ + If oldRoot is a nil root, just copy or move the children to newRoot. + If not a nil root, make oldRoot a child of newRoot. + + old=^(nil a b c), new=r yields ^(r a b c) + old=^(a b c), new=r yields ^(r ^(a b c)) + + If newRoot is a nil-rooted single child tree, use the single + child as the new root node. + + old=^(nil a b c), new=^(nil r) yields ^(r a b c) + old=^(a b c), new=^(nil r) yields ^(r ^(a b c)) + + If oldRoot was null, it's ok, just return newRoot (even if isNil). + + old=null, new=r yields r + old=null, new=^(nil r) yields ^(nil r) + + Return newRoot. Throw an exception if newRoot is not a + simple node or nil root with a single child node--it must be a root + node. If newRoot is ^(nil x) return x as newRoot. + + Be advised that it's ok for newRoot to point at oldRoot's + children; i.e., you don't have to copy the list. We are + constructing these nodes so we should have this control for + efficiency. + """ + + raise NotImplementedError + + + def rulePostProcessing(self, root): + """ + Given the root of the subtree created for this rule, post process + it to do any simplifications or whatever you want. A required + behavior is to convert ^(nil singleSubtree) to singleSubtree + as the setting of start/stop indexes relies on a single non-nil root + for non-flat trees. + + Flat trees such as for lists like "idlist : ID+ ;" are left alone + unless there is only one ID. For a list, the start/stop indexes + are set in the nil node. + + This method is executed after all rule tree construction and right + before setTokenBoundaries(). + """ + + raise NotImplementedError + + + def getUniqueID(self, node): + """For identifying trees. + + How to identify nodes so we can say "add node to a prior node"? + Even becomeRoot is an issue. Use System.identityHashCode(node) + usually. + """ + + raise NotImplementedError + + + # R e w r i t e R u l e s + + def createFromToken(self, tokenType, fromToken, text=None): + """ + Create a new node derived from a token, with a new token type and + (optionally) new text. + + This is invoked from an imaginary node ref on right side of a + rewrite rule as IMAG[$tokenLabel] or IMAG[$tokenLabel "IMAG"]. + + This should invoke createToken(Token). + """ + + raise NotImplementedError + + + def createFromType(self, tokenType, text): + """Create a new node derived from a token, with a new token type. + + This is invoked from an imaginary node ref on right side of a + rewrite rule as IMAG["IMAG"]. + + This should invoke createToken(int,String). + """ + + raise NotImplementedError + + + # C o n t e n t + + def getType(self, t): + """For tree parsing, I need to know the token type of a node""" + + raise NotImplementedError + + + def setType(self, t, type): + """Node constructors can set the type of a node""" + + raise NotImplementedError + + + def getText(self, t): + raise NotImplementedError + + def setText(self, t, text): + """Node constructors can set the text of a node""" + + raise NotImplementedError + + + def getToken(self, t): + """Return the token object from which this node was created. + + Currently used only for printing an error message. + The error display routine in BaseRecognizer needs to + display where the input the error occurred. If your + tree of limitation does not store information that can + lead you to the token, you can create a token filled with + the appropriate information and pass that back. See + BaseRecognizer.getErrorMessage(). + """ + + raise NotImplementedError + + + def setTokenBoundaries(self, t, startToken, stopToken): + """ + Where are the bounds in the input token stream for this node and + all children? Each rule that creates AST nodes will call this + method right before returning. Flat trees (i.e., lists) will + still usually have a nil root node just to hold the children list. + That node would contain the start/stop indexes then. + """ + + raise NotImplementedError + + + def getTokenStartIndex(self, t): + """ + Get the token start index for this subtree; return -1 if no such index + """ + + raise NotImplementedError + + + def getTokenStopIndex(self, t): + """ + Get the token stop index for this subtree; return -1 if no such index + """ + + raise NotImplementedError + + + # N a v i g a t i o n / T r e e P a r s i n g + + def getChild(self, t, i): + """Get a child 0..n-1 node""" + + raise NotImplementedError + + + def setChild(self, t, i, child): + """Set ith child (0..n-1) to t; t must be non-null and non-nil node""" + + raise NotImplementedError + + + def deleteChild(self, t, i): + """Remove ith child and shift children down from right.""" + + raise NotImplementedError + + + def getChildCount(self, t): + """How many children? If 0, then this is a leaf node""" + + raise NotImplementedError + + + def getParent(self, t): + """ + Who is the parent node of this node; if null, implies node is root. + If your node type doesn't handle this, it's ok but the tree rewrites + in tree parsers need this functionality. + """ + + raise NotImplementedError + + + def setParent(self, t, parent): + """ + Who is the parent node of this node; if null, implies node is root. + If your node type doesn't handle this, it's ok but the tree rewrites + in tree parsers need this functionality. + """ + + raise NotImplementedError + + + def getChildIndex(self, t): + """ + What index is this node in the child list? Range: 0..n-1 + If your node type doesn't handle this, it's ok but the tree rewrites + in tree parsers need this functionality. + """ + + raise NotImplementedError + + + def setChildIndex(self, t, index): + """ + What index is this node in the child list? Range: 0..n-1 + If your node type doesn't handle this, it's ok but the tree rewrites + in tree parsers need this functionality. + """ + + raise NotImplementedError + + + def replaceChildren(self, parent, startChildIndex, stopChildIndex, t): + """ + Replace from start to stop child index of parent with t, which might + be a list. Number of children may be different + after this call. + + If parent is null, don't do anything; must be at root of overall tree. + Can't replace whatever points to the parent externally. Do nothing. + """ + + raise NotImplementedError + + + # Misc + + def create(self, *args): + """ + Deprecated, use createWithPayload, createFromToken or createFromType. + + This method only exists to mimic the Java interface of TreeAdaptor. + + """ + + if len(args) == 1 and isinstance(args[0], Token): + # Object create(Token payload); +## warnings.warn( +## "Using create() is deprecated, use createWithPayload()", +## DeprecationWarning, +## stacklevel=2 +## ) + return self.createWithPayload(args[0]) + + if (len(args) == 2 + and isinstance(args[0], (int, long)) + and isinstance(args[1], Token) + ): + # Object create(int tokenType, Token fromToken); +## warnings.warn( +## "Using create() is deprecated, use createFromToken()", +## DeprecationWarning, +## stacklevel=2 +## ) + return self.createFromToken(args[0], args[1]) + + if (len(args) == 3 + and isinstance(args[0], (int, long)) + and isinstance(args[1], Token) + and isinstance(args[2], basestring) + ): + # Object create(int tokenType, Token fromToken, String text); +## warnings.warn( +## "Using create() is deprecated, use createFromToken()", +## DeprecationWarning, +## stacklevel=2 +## ) + return self.createFromToken(args[0], args[1], args[2]) + + if (len(args) == 2 + and isinstance(args[0], (int, long)) + and isinstance(args[1], basestring) + ): + # Object create(int tokenType, String text); +## warnings.warn( +## "Using create() is deprecated, use createFromType()", +## DeprecationWarning, +## stacklevel=2 +## ) + return self.createFromType(args[0], args[1]) + + raise TypeError( + "No create method with this signature found: %s" + % (', '.join(type(v).__name__ for v in args)) + ) + + +############################################################################ +# +# base implementation of Tree and TreeAdaptor +# +# Tree +# \- BaseTree +# +# TreeAdaptor +# \- BaseTreeAdaptor +# +############################################################################ + + +class BaseTree(Tree): + """ + @brief A generic tree implementation with no payload. + + You must subclass to + actually have any user data. ANTLR v3 uses a list of children approach + instead of the child-sibling approach in v2. A flat tree (a list) is + an empty node whose children represent the list. An empty, but + non-null node is called "nil". + """ + + # BaseTree is abstract, no need to complain about not implemented abstract + # methods + # pylint: disable-msg=W0223 + + def __init__(self, node=None): + """ + Create a new node from an existing node does nothing for BaseTree + as there are no fields other than the children list, which cannot + be copied as the children are not considered part of this node. + """ + + Tree.__init__(self) + self.children = [] + self.parent = None + self.childIndex = 0 + + + def getChild(self, i): + try: + return self.children[i] + except IndexError: + return None + + + def getChildren(self): + """@brief Get the children internal List + + Note that if you directly mess with + the list, do so at your own risk. + """ + + # FIXME: mark as deprecated + return self.children + + + def getFirstChildWithType(self, treeType): + for child in self.children: + if child.getType() == treeType: + return child + + return None + + + def getChildCount(self): + return len(self.children) + + + def addChild(self, childTree): + """Add t as child of this node. + + Warning: if t has no children, but child does + and child isNil then this routine moves children to t via + t.children = child.children; i.e., without copying the array. + """ + + # this implementation is much simpler and probably less efficient + # than the mumbo-jumbo that Ter did for the Java runtime. + + if childTree is None: + return + + if childTree.isNil(): + # t is an empty node possibly with children + + if self.children is childTree.children: + raise ValueError("attempt to add child list to itself") + + # fix parent pointer and childIndex for new children + for idx, child in enumerate(childTree.children): + child.parent = self + child.childIndex = len(self.children) + idx + + self.children += childTree.children + + else: + # child is not nil (don't care about children) + self.children.append(childTree) + childTree.parent = self + childTree.childIndex = len(self.children) - 1 + + + def addChildren(self, children): + """Add all elements of kids list as children of this node""" + + self.children += children + + + def setChild(self, i, t): + if t is None: + return + + if t.isNil(): + raise ValueError("Can't set single child to a list") + + self.children[i] = t + t.parent = self + t.childIndex = i + + + def deleteChild(self, i): + killed = self.children[i] + + del self.children[i] + + # walk rest and decrement their child indexes + for idx, child in enumerate(self.children[i:]): + child.childIndex = i + idx + + return killed + + + def replaceChildren(self, startChildIndex, stopChildIndex, newTree): + """ + Delete children from start to stop and replace with t even if t is + a list (nil-root tree). num of children can increase or decrease. + For huge child lists, inserting children can force walking rest of + children to set their childindex; could be slow. + """ + + if (startChildIndex >= len(self.children) + or stopChildIndex >= len(self.children) + ): + raise IndexError("indexes invalid") + + replacingHowMany = stopChildIndex - startChildIndex + 1 + + # normalize to a list of children to add: newChildren + if newTree.isNil(): + newChildren = newTree.children + + else: + newChildren = [newTree] + + replacingWithHowMany = len(newChildren) + delta = replacingHowMany - replacingWithHowMany + + + if delta == 0: + # if same number of nodes, do direct replace + for idx, child in enumerate(newChildren): + self.children[idx + startChildIndex] = child + child.parent = self + child.childIndex = idx + startChildIndex + + else: + # length of children changes... + + # ...delete replaced segment... + del self.children[startChildIndex:stopChildIndex+1] + + # ...insert new segment... + self.children[startChildIndex:startChildIndex] = newChildren + + # ...and fix indeces + self.freshenParentAndChildIndexes(startChildIndex) + + + def isNil(self): + return False + + + def freshenParentAndChildIndexes(self, offset=0): + for idx, child in enumerate(self.children[offset:]): + child.childIndex = idx + offset + child.parent = self + + + def sanityCheckParentAndChildIndexes(self, parent=None, i=-1): + if parent != self.parent: + raise ValueError( + "parents don't match; expected %r found %r" + % (parent, self.parent) + ) + + if i != self.childIndex: + raise ValueError( + "child indexes don't match; expected %d found %d" + % (i, self.childIndex) + ) + + for idx, child in enumerate(self.children): + child.sanityCheckParentAndChildIndexes(self, idx) + + + def getChildIndex(self): + """BaseTree doesn't track child indexes.""" + + return 0 + + + def setChildIndex(self, index): + """BaseTree doesn't track child indexes.""" + + pass + + + def getParent(self): + """BaseTree doesn't track parent pointers.""" + + return None + + def setParent(self, t): + """BaseTree doesn't track parent pointers.""" + + pass + + + def toStringTree(self): + """Print out a whole tree not just a node""" + + if len(self.children) == 0: + return self.toString() + + buf = [] + if not self.isNil(): + buf.append('(') + buf.append(self.toString()) + buf.append(' ') + + for i, child in enumerate(self.children): + if i > 0: + buf.append(' ') + buf.append(child.toStringTree()) + + if not self.isNil(): + buf.append(')') + + return ''.join(buf) + + + def getLine(self): + return 0 + + + def getCharPositionInLine(self): + return 0 + + + def toString(self): + """Override to say how a node (not a tree) should look as text""" + + raise NotImplementedError + + + +class BaseTreeAdaptor(TreeAdaptor): + """ + @brief A TreeAdaptor that works with any Tree implementation. + """ + + # BaseTreeAdaptor is abstract, no need to complain about not implemented + # abstract methods + # pylint: disable-msg=W0223 + + def nil(self): + return self.createWithPayload(None) + + + def errorNode(self, input, start, stop, exc): + """ + create tree node that holds the start and stop tokens associated + with an error. + + If you specify your own kind of tree nodes, you will likely have to + override this method. CommonTree returns Token.INVALID_TOKEN_TYPE + if no token payload but you might have to set token type for diff + node type. + """ + + return CommonErrorNode(input, start, stop, exc) + + + def isNil(self, tree): + return tree.isNil() + + + def dupTree(self, t, parent=None): + """ + This is generic in the sense that it will work with any kind of + tree (not just Tree interface). It invokes the adaptor routines + not the tree node routines to do the construction. + """ + + if t is None: + return None + + newTree = self.dupNode(t) + + # ensure new subtree root has parent/child index set + + # same index in new tree + self.setChildIndex(newTree, self.getChildIndex(t)) + + self.setParent(newTree, parent) + + for i in range(self.getChildCount(t)): + child = self.getChild(t, i) + newSubTree = self.dupTree(child, t) + self.addChild(newTree, newSubTree) + + return newTree + + + def addChild(self, tree, child): + """ + Add a child to the tree t. If child is a flat tree (a list), make all + in list children of t. Warning: if t has no children, but child does + and child isNil then you can decide it is ok to move children to t via + t.children = child.children; i.e., without copying the array. Just + make sure that this is consistent with have the user will build + ASTs. + """ + + #if isinstance(child, Token): + # child = self.createWithPayload(child) + + if tree is not None and child is not None: + tree.addChild(child) + + + def becomeRoot(self, newRoot, oldRoot): + """ + If oldRoot is a nil root, just copy or move the children to newRoot. + If not a nil root, make oldRoot a child of newRoot. + + old=^(nil a b c), new=r yields ^(r a b c) + old=^(a b c), new=r yields ^(r ^(a b c)) + + If newRoot is a nil-rooted single child tree, use the single + child as the new root node. + + old=^(nil a b c), new=^(nil r) yields ^(r a b c) + old=^(a b c), new=^(nil r) yields ^(r ^(a b c)) + + If oldRoot was null, it's ok, just return newRoot (even if isNil). + + old=null, new=r yields r + old=null, new=^(nil r) yields ^(nil r) + + Return newRoot. Throw an exception if newRoot is not a + simple node or nil root with a single child node--it must be a root + node. If newRoot is ^(nil x) return x as newRoot. + + Be advised that it's ok for newRoot to point at oldRoot's + children; i.e., you don't have to copy the list. We are + constructing these nodes so we should have this control for + efficiency. + """ + + if isinstance(newRoot, Token): + newRoot = self.create(newRoot) + + if oldRoot is None: + return newRoot + + if not isinstance(newRoot, CommonTree): + newRoot = self.createWithPayload(newRoot) + + # handle ^(nil real-node) + if newRoot.isNil(): + nc = newRoot.getChildCount() + if nc == 1: + newRoot = newRoot.getChild(0) + + elif nc > 1: + # TODO: make tree run time exceptions hierarchy + raise RuntimeError("more than one node as root") + + # add oldRoot to newRoot; addChild takes care of case where oldRoot + # is a flat list (i.e., nil-rooted tree). All children of oldRoot + # are added to newRoot. + newRoot.addChild(oldRoot) + return newRoot + + + def rulePostProcessing(self, root): + """Transform ^(nil x) to x and nil to null""" + + if root is not None and root.isNil(): + if root.getChildCount() == 0: + root = None + + elif root.getChildCount() == 1: + root = root.getChild(0) + # whoever invokes rule will set parent and child index + root.setParent(None) + root.setChildIndex(-1) + + return root + + + def createFromToken(self, tokenType, fromToken, text=None): + assert isinstance(tokenType, (int, long)), type(tokenType).__name__ + assert isinstance(fromToken, Token), type(fromToken).__name__ + assert text is None or isinstance(text, basestring), type(text).__name__ + + fromToken = self.createToken(fromToken) + fromToken.type = tokenType + if text is not None: + fromToken.text = text + t = self.createWithPayload(fromToken) + return t + + + def createFromType(self, tokenType, text): + assert isinstance(tokenType, (int, long)), type(tokenType).__name__ + assert isinstance(text, basestring), type(text).__name__ + + fromToken = self.createToken(tokenType=tokenType, text=text) + t = self.createWithPayload(fromToken) + return t + + + def getType(self, t): + return t.getType() + + + def setType(self, t, type): + raise RuntimeError("don't know enough about Tree node") + + + def getText(self, t): + return t.getText() + + + def setText(self, t, text): + raise RuntimeError("don't know enough about Tree node") + + + def getChild(self, t, i): + return t.getChild(i) + + + def setChild(self, t, i, child): + t.setChild(i, child) + + + def deleteChild(self, t, i): + return t.deleteChild(i) + + + def getChildCount(self, t): + return t.getChildCount() + + + def getUniqueID(self, node): + return hash(node) + + + def createToken(self, fromToken=None, tokenType=None, text=None): + """ + Tell me how to create a token for use with imaginary token nodes. + For example, there is probably no input symbol associated with imaginary + token DECL, but you need to create it as a payload or whatever for + the DECL node as in ^(DECL type ID). + + If you care what the token payload objects' type is, you should + override this method and any other createToken variant. + """ + + raise NotImplementedError + + +############################################################################ +# +# common tree implementation +# +# Tree +# \- BaseTree +# \- CommonTree +# \- CommonErrorNode +# +# TreeAdaptor +# \- BaseTreeAdaptor +# \- CommonTreeAdaptor +# +############################################################################ + + +class CommonTree(BaseTree): + """@brief A tree node that is wrapper for a Token object. + + After 3.0 release + while building tree rewrite stuff, it became clear that computing + parent and child index is very difficult and cumbersome. Better to + spend the space in every tree node. If you don't want these extra + fields, it's easy to cut them out in your own BaseTree subclass. + + """ + + def __init__(self, payload): + BaseTree.__init__(self) + + # What token indexes bracket all tokens associated with this node + # and below? + self.startIndex = -1 + self.stopIndex = -1 + + # Who is the parent node of this node; if null, implies node is root + self.parent = None + + # What index is this node in the child list? Range: 0..n-1 + self.childIndex = -1 + + # A single token is the payload + if payload is None: + self.token = None + + elif isinstance(payload, CommonTree): + self.token = payload.token + self.startIndex = payload.startIndex + self.stopIndex = payload.stopIndex + + elif payload is None or isinstance(payload, Token): + self.token = payload + + else: + raise TypeError(type(payload).__name__) + + + + def getToken(self): + return self.token + + + def dupNode(self): + return CommonTree(self) + + + def isNil(self): + return self.token is None + + + def getType(self): + if self.token is None: + return INVALID_TOKEN_TYPE + + return self.token.getType() + + type = property(getType) + + + def getText(self): + if self.token is None: + return None + + return self.token.text + + text = property(getText) + + + def getLine(self): + if self.token is None or self.token.getLine() == 0: + if self.getChildCount(): + return self.getChild(0).getLine() + else: + return 0 + + return self.token.getLine() + + line = property(getLine) + + + def getCharPositionInLine(self): + if self.token is None or self.token.getCharPositionInLine() == -1: + if self.getChildCount(): + return self.getChild(0).getCharPositionInLine() + else: + return 0 + + else: + return self.token.getCharPositionInLine() + + charPositionInLine = property(getCharPositionInLine) + + + def getTokenStartIndex(self): + if self.startIndex == -1 and self.token is not None: + return self.token.getTokenIndex() + + return self.startIndex + + def setTokenStartIndex(self, index): + self.startIndex = index + + tokenStartIndex = property(getTokenStartIndex, setTokenStartIndex) + + + def getTokenStopIndex(self): + if self.stopIndex == -1 and self.token is not None: + return self.token.getTokenIndex() + + return self.stopIndex + + def setTokenStopIndex(self, index): + self.stopIndex = index + + tokenStopIndex = property(getTokenStopIndex, setTokenStopIndex) + + + def getChildIndex(self): + #FIXME: mark as deprecated + return self.childIndex + + + def setChildIndex(self, idx): + #FIXME: mark as deprecated + self.childIndex = idx + + + def getParent(self): + #FIXME: mark as deprecated + return self.parent + + + def setParent(self, t): + #FIXME: mark as deprecated + self.parent = t + + + def toString(self): + if self.isNil(): + return "nil" + + if self.getType() == INVALID_TOKEN_TYPE: + return "" + + return self.token.text + + __str__ = toString + + + + def toStringTree(self): + if not self.children: + return self.toString() + + ret = '' + if not self.isNil(): + ret += '(%s ' % (self.toString()) + + ret += ' '.join([child.toStringTree() for child in self.children]) + + if not self.isNil(): + ret += ')' + + return ret + + +INVALID_NODE = CommonTree(INVALID_TOKEN) + + +class CommonErrorNode(CommonTree): + """A node representing erroneous token range in token stream""" + + def __init__(self, input, start, stop, exc): + CommonTree.__init__(self, None) + + if (stop is None or + (stop.getTokenIndex() < start.getTokenIndex() and + stop.getType() != EOF + ) + ): + # sometimes resync does not consume a token (when LT(1) is + # in follow set. So, stop will be 1 to left to start. adjust. + # Also handle case where start is the first token and no token + # is consumed during recovery; LT(-1) will return null. + stop = start + + self.input = input + self.start = start + self.stop = stop + self.trappedException = exc + + + def isNil(self): + return False + + + def getType(self): + return INVALID_TOKEN_TYPE + + + def getText(self): + if isinstance(self.start, Token): + i = self.start.getTokenIndex() + j = self.stop.getTokenIndex() + if self.stop.getType() == EOF: + j = self.input.size() + + badText = self.input.toString(i, j) + + elif isinstance(self.start, Tree): + badText = self.input.toString(self.start, self.stop) + + else: + # people should subclass if they alter the tree type so this + # next one is for sure correct. + badText = "" + + return badText + + + def toString(self): + if isinstance(self.trappedException, MissingTokenException): + return ("") + + elif isinstance(self.trappedException, UnwantedTokenException): + return ("") + + elif isinstance(self.trappedException, MismatchedTokenException): + return ("") + + elif isinstance(self.trappedException, NoViableAltException): + return ("") + + return "" + + +class CommonTreeAdaptor(BaseTreeAdaptor): + """ + @brief A TreeAdaptor that works with any Tree implementation. + + It provides + really just factory methods; all the work is done by BaseTreeAdaptor. + If you would like to have different tokens created than ClassicToken + objects, you need to override this and then set the parser tree adaptor to + use your subclass. + + To get your parser to build nodes of a different type, override + create(Token). + """ + + def dupNode(self, treeNode): + """ + Duplicate a node. This is part of the factory; + override if you want another kind of node to be built. + + I could use reflection to prevent having to override this + but reflection is slow. + """ + + if treeNode is None: + return None + + return treeNode.dupNode() + + + def createWithPayload(self, payload): + return CommonTree(payload) + + + def createToken(self, fromToken=None, tokenType=None, text=None): + """ + Tell me how to create a token for use with imaginary token nodes. + For example, there is probably no input symbol associated with imaginary + token DECL, but you need to create it as a payload or whatever for + the DECL node as in ^(DECL type ID). + + If you care what the token payload objects' type is, you should + override this method and any other createToken variant. + """ + + if fromToken is not None: + return CommonToken(oldToken=fromToken) + + return CommonToken(type=tokenType, text=text) + + + def setTokenBoundaries(self, t, startToken, stopToken): + """ + Track start/stop token for subtree root created for a rule. + Only works with Tree nodes. For rules that match nothing, + seems like this will yield start=i and stop=i-1 in a nil node. + Might be useful info so I'll not force to be i..i. + """ + + if t is None: + return + + start = 0 + stop = 0 + + if startToken is not None: + start = startToken.index + + if stopToken is not None: + stop = stopToken.index + + t.setTokenStartIndex(start) + t.setTokenStopIndex(stop) + + + def getTokenStartIndex(self, t): + if t is None: + return -1 + return t.getTokenStartIndex() + + + def getTokenStopIndex(self, t): + if t is None: + return -1 + return t.getTokenStopIndex() + + + def getText(self, t): + if t is None: + return None + return t.getText() + + + def getType(self, t): + if t is None: + return INVALID_TOKEN_TYPE + + return t.getType() + + + def getToken(self, t): + """ + What is the Token associated with this node? If + you are not using CommonTree, then you must + override this in your own adaptor. + """ + + if isinstance(t, CommonTree): + return t.getToken() + + return None # no idea what to do + + + def getChild(self, t, i): + if t is None: + return None + return t.getChild(i) + + + def getChildCount(self, t): + if t is None: + return 0 + return t.getChildCount() + + + def getParent(self, t): + return t.getParent() + + + def setParent(self, t, parent): + t.setParent(parent) + + + def getChildIndex(self, t): + return t.getChildIndex() + + + def setChildIndex(self, t, index): + t.setChildIndex(index) + + + def replaceChildren(self, parent, startChildIndex, stopChildIndex, t): + if parent is not None: + parent.replaceChildren(startChildIndex, stopChildIndex, t) + + +############################################################################ +# +# streams +# +# TreeNodeStream +# \- BaseTree +# \- CommonTree +# +# TreeAdaptor +# \- BaseTreeAdaptor +# \- CommonTreeAdaptor +# +############################################################################ + + + +class TreeNodeStream(IntStream): + """@brief A stream of tree nodes + + It accessing nodes from a tree of some kind. + """ + + # TreeNodeStream is abstract, no need to complain about not implemented + # abstract methods + # pylint: disable-msg=W0223 + + def get(self, i): + """Get a tree node at an absolute index i; 0..n-1. + If you don't want to buffer up nodes, then this method makes no + sense for you. + """ + + raise NotImplementedError + + + def LT(self, k): + """ + Get tree node at current input pointer + i ahead where i=1 is next node. + i<0 indicates nodes in the past. So LT(-1) is previous node, but + implementations are not required to provide results for k < -1. + LT(0) is undefined. For i>=n, return null. + Return null for LT(0) and any index that results in an absolute address + that is negative. + + This is analogus to the LT() method of the TokenStream, but this + returns a tree node instead of a token. Makes code gen identical + for both parser and tree grammars. :) + """ + + raise NotImplementedError + + + def getTreeSource(self): + """ + Where is this stream pulling nodes from? This is not the name, but + the object that provides node objects. + """ + + raise NotImplementedError + + + def getTokenStream(self): + """ + If the tree associated with this stream was created from a TokenStream, + you can specify it here. Used to do rule $text attribute in tree + parser. Optional unless you use tree parser rule text attribute + or output=template and rewrite=true options. + """ + + raise NotImplementedError + + + def getTreeAdaptor(self): + """ + What adaptor can tell me how to interpret/navigate nodes and + trees. E.g., get text of a node. + """ + + raise NotImplementedError + + + def setUniqueNavigationNodes(self, uniqueNavigationNodes): + """ + As we flatten the tree, we use UP, DOWN nodes to represent + the tree structure. When debugging we need unique nodes + so we have to instantiate new ones. When doing normal tree + parsing, it's slow and a waste of memory to create unique + navigation nodes. Default should be false; + """ + + raise NotImplementedError + + + def toString(self, start, stop): + """ + Return the text of all nodes from start to stop, inclusive. + If the stream does not buffer all the nodes then it can still + walk recursively from start until stop. You can always return + null or "" too, but users should not access $ruleLabel.text in + an action of course in that case. + """ + + raise NotImplementedError + + + # REWRITING TREES (used by tree parser) + def replaceChildren(self, parent, startChildIndex, stopChildIndex, t): + """ + Replace from start to stop child index of parent with t, which might + be a list. Number of children may be different + after this call. The stream is notified because it is walking the + tree and might need to know you are monkeying with the underlying + tree. Also, it might be able to modify the node stream to avoid + restreaming for future phases. + + If parent is null, don't do anything; must be at root of overall tree. + Can't replace whatever points to the parent externally. Do nothing. + """ + + raise NotImplementedError + + +class CommonTreeNodeStream(TreeNodeStream): + """@brief A buffered stream of tree nodes. + + Nodes can be from a tree of ANY kind. + + This node stream sucks all nodes out of the tree specified in + the constructor during construction and makes pointers into + the tree using an array of Object pointers. The stream necessarily + includes pointers to DOWN and UP and EOF nodes. + + This stream knows how to mark/release for backtracking. + + This stream is most suitable for tree interpreters that need to + jump around a lot or for tree parsers requiring speed (at cost of memory). + There is some duplicated functionality here with UnBufferedTreeNodeStream + but just in bookkeeping, not tree walking etc... + + @see UnBufferedTreeNodeStream + """ + + def __init__(self, *args): + TreeNodeStream.__init__(self) + + if len(args) == 1: + adaptor = CommonTreeAdaptor() + tree = args[0] + + elif len(args) == 2: + adaptor = args[0] + tree = args[1] + + else: + raise TypeError("Invalid arguments") + + # all these navigation nodes are shared and hence they + # cannot contain any line/column info + self.down = adaptor.createFromType(DOWN, "DOWN") + self.up = adaptor.createFromType(UP, "UP") + self.eof = adaptor.createFromType(EOF, "EOF") + + # The complete mapping from stream index to tree node. + # This buffer includes pointers to DOWN, UP, and EOF nodes. + # It is built upon ctor invocation. The elements are type + # Object as we don't what the trees look like. + + # Load upon first need of the buffer so we can set token types + # of interest for reverseIndexing. Slows us down a wee bit to + # do all of the if p==-1 testing everywhere though. + self.nodes = [] + + # Pull nodes from which tree? + self.root = tree + + # IF this tree (root) was created from a token stream, track it. + self.tokens = None + + # What tree adaptor was used to build these trees + self.adaptor = adaptor + + # Reuse same DOWN, UP navigation nodes unless this is true + self.uniqueNavigationNodes = False + + # The index into the nodes list of the current node (next node + # to consume). If -1, nodes array not filled yet. + self.p = -1 + + # Track the last mark() call result value for use in rewind(). + self.lastMarker = None + + # Stack of indexes used for push/pop calls + self.calls = [] + + + def fillBuffer(self): + """Walk tree with depth-first-search and fill nodes buffer. + Don't do DOWN, UP nodes if its a list (t is isNil). + """ + + self._fillBuffer(self.root) + self.p = 0 # buffer of nodes intialized now + + + def _fillBuffer(self, t): + nil = self.adaptor.isNil(t) + + if not nil: + self.nodes.append(t) # add this node + + # add DOWN node if t has children + n = self.adaptor.getChildCount(t) + if not nil and n > 0: + self.addNavigationNode(DOWN) + + # and now add all its children + for c in range(n): + self._fillBuffer(self.adaptor.getChild(t, c)) + + # add UP node if t has children + if not nil and n > 0: + self.addNavigationNode(UP) + + + def getNodeIndex(self, node): + """What is the stream index for node? 0..n-1 + Return -1 if node not found. + """ + + if self.p == -1: + self.fillBuffer() + + for i, t in enumerate(self.nodes): + if t == node: + return i + + return -1 + + + def addNavigationNode(self, ttype): + """ + As we flatten the tree, we use UP, DOWN nodes to represent + the tree structure. When debugging we need unique nodes + so instantiate new ones when uniqueNavigationNodes is true. + """ + + navNode = None + + if ttype == DOWN: + if self.hasUniqueNavigationNodes(): + navNode = self.adaptor.createFromType(DOWN, "DOWN") + + else: + navNode = self.down + + else: + if self.hasUniqueNavigationNodes(): + navNode = self.adaptor.createFromType(UP, "UP") + + else: + navNode = self.up + + self.nodes.append(navNode) + + + def get(self, i): + if self.p == -1: + self.fillBuffer() + + return self.nodes[i] + + + def LT(self, k): + if self.p == -1: + self.fillBuffer() + + if k == 0: + return None + + if k < 0: + return self.LB(-k) + + #System.out.print("LT(p="+p+","+k+")="); + if self.p + k - 1 >= len(self.nodes): + return self.eof + + return self.nodes[self.p + k - 1] + + + def getCurrentSymbol(self): + return self.LT(1) + + + def LB(self, k): + """Look backwards k nodes""" + + if k == 0: + return None + + if self.p - k < 0: + return None + + return self.nodes[self.p - k] + + + def getTreeSource(self): + return self.root + + + def getSourceName(self): + return self.getTokenStream().getSourceName() + + + def getTokenStream(self): + return self.tokens + + + def setTokenStream(self, tokens): + self.tokens = tokens + + + def getTreeAdaptor(self): + return self.adaptor + + + def hasUniqueNavigationNodes(self): + return self.uniqueNavigationNodes + + + def setUniqueNavigationNodes(self, uniqueNavigationNodes): + self.uniqueNavigationNodes = uniqueNavigationNodes + + + def consume(self): + if self.p == -1: + self.fillBuffer() + + self.p += 1 + + + def LA(self, i): + return self.adaptor.getType(self.LT(i)) + + + def mark(self): + if self.p == -1: + self.fillBuffer() + + + self.lastMarker = self.index() + return self.lastMarker + + + def release(self, marker=None): + # no resources to release + + pass + + + def index(self): + return self.p + + + def rewind(self, marker=None): + if marker is None: + marker = self.lastMarker + + self.seek(marker) + + + def seek(self, index): + if self.p == -1: + self.fillBuffer() + + self.p = index + + + def push(self, index): + """ + Make stream jump to a new location, saving old location. + Switch back with pop(). + """ + + self.calls.append(self.p) # save current index + self.seek(index) + + + def pop(self): + """ + Seek back to previous index saved during last push() call. + Return top of stack (return index). + """ + + ret = self.calls.pop(-1) + self.seek(ret) + return ret + + + def reset(self): + self.p = 0 + self.lastMarker = 0 + self.calls = [] + + + def size(self): + if self.p == -1: + self.fillBuffer() + + return len(self.nodes) + + + # TREE REWRITE INTERFACE + + def replaceChildren(self, parent, startChildIndex, stopChildIndex, t): + if parent is not None: + self.adaptor.replaceChildren( + parent, startChildIndex, stopChildIndex, t + ) + + + def __str__(self): + """Used for testing, just return the token type stream""" + + if self.p == -1: + self.fillBuffer() + + return ' '.join([str(self.adaptor.getType(node)) + for node in self.nodes + ]) + + + def toString(self, start, stop): + if start is None or stop is None: + return None + + if self.p == -1: + self.fillBuffer() + + #System.out.println("stop: "+stop); + #if ( start instanceof CommonTree ) + # System.out.print("toString: "+((CommonTree)start).getToken()+", "); + #else + # System.out.println(start); + #if ( stop instanceof CommonTree ) + # System.out.println(((CommonTree)stop).getToken()); + #else + # System.out.println(stop); + + # if we have the token stream, use that to dump text in order + if self.tokens is not None: + beginTokenIndex = self.adaptor.getTokenStartIndex(start) + endTokenIndex = self.adaptor.getTokenStopIndex(stop) + + # if it's a tree, use start/stop index from start node + # else use token range from start/stop nodes + if self.adaptor.getType(stop) == UP: + endTokenIndex = self.adaptor.getTokenStopIndex(start) + + elif self.adaptor.getType(stop) == EOF: + endTokenIndex = self.size() -2 # don't use EOF + + return self.tokens.toString(beginTokenIndex, endTokenIndex) + + # walk nodes looking for start + i, t = 0, None + for i, t in enumerate(self.nodes): + if t == start: + break + + # now walk until we see stop, filling string buffer with text + buf = [] + t = self.nodes[i] + while t != stop: + text = self.adaptor.getText(t) + if text is None: + text = " " + self.adaptor.getType(t) + + buf.append(text) + i += 1 + t = self.nodes[i] + + # include stop node too + text = self.adaptor.getText(stop) + if text is None: + text = " " +self.adaptor.getType(stop) + + buf.append(text) + + return ''.join(buf) + + + ## iterator interface + def __iter__(self): + if self.p == -1: + self.fillBuffer() + + for node in self.nodes: + yield node + + +############################################################################# +# +# tree parser +# +############################################################################# + +class TreeParser(BaseRecognizer): + """@brief Baseclass for generated tree parsers. + + A parser for a stream of tree nodes. "tree grammars" result in a subclass + of this. All the error reporting and recovery is shared with Parser via + the BaseRecognizer superclass. + """ + + def __init__(self, input, state=None): + BaseRecognizer.__init__(self, state) + + self.input = None + self.setTreeNodeStream(input) + + + def reset(self): + BaseRecognizer.reset(self) # reset all recognizer state variables + if self.input is not None: + self.input.seek(0) # rewind the input + + + def setTreeNodeStream(self, input): + """Set the input stream""" + + self.input = input + + + def getTreeNodeStream(self): + return self.input + + + def getSourceName(self): + return self.input.getSourceName() + + + def getCurrentInputSymbol(self, input): + return input.LT(1) + + + def getMissingSymbol(self, input, e, expectedTokenType, follow): + tokenText = "" + return CommonTree(CommonToken(type=expectedTokenType, text=tokenText)) + + + def matchAny(self, ignore): # ignore stream, copy of this.input + """ + Match '.' in tree parser has special meaning. Skip node or + entire tree if node has children. If children, scan until + corresponding UP node. + """ + + self._state.errorRecovery = False + + look = self.input.LT(1) + if self.input.getTreeAdaptor().getChildCount(look) == 0: + self.input.consume() # not subtree, consume 1 node and return + return + + # current node is a subtree, skip to corresponding UP. + # must count nesting level to get right UP + level = 0 + tokenType = self.input.getTreeAdaptor().getType(look) + while tokenType != EOF and not (tokenType == UP and level==0): + self.input.consume() + look = self.input.LT(1) + tokenType = self.input.getTreeAdaptor().getType(look) + if tokenType == DOWN: + level += 1 + + elif tokenType == UP: + level -= 1 + + self.input.consume() # consume UP + + + def mismatch(self, input, ttype, follow): + """ + We have DOWN/UP nodes in the stream that have no line info; override. + plus we want to alter the exception type. Don't try to recover + from tree parser errors inline... + """ + + raise MismatchedTreeNodeException(ttype, input) + + + def getErrorHeader(self, e): + """ + Prefix error message with the grammar name because message is + always intended for the programmer because the parser built + the input tree not the user. + """ + + return (self.getGrammarFileName() + + ": node from %sline %s:%s" + % (['', "after "][e.approximateLineInfo], + e.line, + e.charPositionInLine + ) + ) + + def getErrorMessage(self, e, tokenNames): + """ + Tree parsers parse nodes they usually have a token object as + payload. Set the exception token and do the default behavior. + """ + + if isinstance(self, TreeParser): + adaptor = e.input.getTreeAdaptor() + e.token = adaptor.getToken(e.node) + if e.token is not None: # could be an UP/DOWN node + e.token = CommonToken( + type=adaptor.getType(e.node), + text=adaptor.getText(e.node) + ) + + return BaseRecognizer.getErrorMessage(self, e, tokenNames) + + + def traceIn(self, ruleName, ruleIndex): + BaseRecognizer.traceIn(self, ruleName, ruleIndex, self.input.LT(1)) + + + def traceOut(self, ruleName, ruleIndex): + BaseRecognizer.traceOut(self, ruleName, ruleIndex, self.input.LT(1)) + + +############################################################################# +# +# streams for rule rewriting +# +############################################################################# + +class RewriteRuleElementStream(object): + """@brief Internal helper class. + + A generic list of elements tracked in an alternative to be used in + a -> rewrite rule. We need to subclass to fill in the next() method, + which returns either an AST node wrapped around a token payload or + an existing subtree. + + Once you start next()ing, do not try to add more elements. It will + break the cursor tracking I believe. + + @see org.antlr.runtime.tree.RewriteRuleSubtreeStream + @see org.antlr.runtime.tree.RewriteRuleTokenStream + + TODO: add mechanism to detect/puke on modification after reading from + stream + """ + + def __init__(self, adaptor, elementDescription, elements=None): + # Cursor 0..n-1. If singleElement!=null, cursor is 0 until you next(), + # which bumps it to 1 meaning no more elements. + self.cursor = 0 + + # Track single elements w/o creating a list. Upon 2nd add, alloc list + self.singleElement = None + + # The list of tokens or subtrees we are tracking + self.elements = None + + # Once a node / subtree has been used in a stream, it must be dup'd + # from then on. Streams are reset after subrules so that the streams + # can be reused in future subrules. So, reset must set a dirty bit. + # If dirty, then next() always returns a dup. + self.dirty = False + + # The element or stream description; usually has name of the token or + # rule reference that this list tracks. Can include rulename too, but + # the exception would track that info. + self.elementDescription = elementDescription + + self.adaptor = adaptor + + if isinstance(elements, (list, tuple)): + # Create a stream, but feed off an existing list + self.singleElement = None + self.elements = elements + + else: + # Create a stream with one element + self.add(elements) + + + def reset(self): + """ + Reset the condition of this stream so that it appears we have + not consumed any of its elements. Elements themselves are untouched. + Once we reset the stream, any future use will need duplicates. Set + the dirty bit. + """ + + self.cursor = 0 + self.dirty = True + + + def add(self, el): + if el is None: + return + + if self.elements is not None: # if in list, just add + self.elements.append(el) + return + + if self.singleElement is None: # no elements yet, track w/o list + self.singleElement = el + return + + # adding 2nd element, move to list + self.elements = [] + self.elements.append(self.singleElement) + self.singleElement = None + self.elements.append(el) + + + def nextTree(self): + """ + Return the next element in the stream. If out of elements, throw + an exception unless size()==1. If size is 1, then return elements[0]. + + Return a duplicate node/subtree if stream is out of elements and + size==1. If we've already used the element, dup (dirty bit set). + """ + + if (self.dirty + or (self.cursor >= len(self) and len(self) == 1) + ): + # if out of elements and size is 1, dup + el = self._next() + return self.dup(el) + + # test size above then fetch + el = self._next() + return el + + + def _next(self): + """ + do the work of getting the next element, making sure that it's + a tree node or subtree. Deal with the optimization of single- + element list versus list of size > 1. Throw an exception + if the stream is empty or we're out of elements and size>1. + protected so you can override in a subclass if necessary. + """ + + if len(self) == 0: + raise RewriteEmptyStreamException(self.elementDescription) + + if self.cursor >= len(self): # out of elements? + if len(self) == 1: # if size is 1, it's ok; return and we'll dup + return self.toTree(self.singleElement) + + # out of elements and size was not 1, so we can't dup + raise RewriteCardinalityException(self.elementDescription) + + # we have elements + if self.singleElement is not None: + self.cursor += 1 # move cursor even for single element list + return self.toTree(self.singleElement) + + # must have more than one in list, pull from elements + o = self.toTree(self.elements[self.cursor]) + self.cursor += 1 + return o + + + def dup(self, el): + """ + When constructing trees, sometimes we need to dup a token or AST + subtree. Dup'ing a token means just creating another AST node + around it. For trees, you must call the adaptor.dupTree() unless + the element is for a tree root; then it must be a node dup. + """ + + raise NotImplementedError + + + def toTree(self, el): + """ + Ensure stream emits trees; tokens must be converted to AST nodes. + AST nodes can be passed through unmolested. + """ + + return el + + + def hasNext(self): + return ( (self.singleElement is not None and self.cursor < 1) + or (self.elements is not None + and self.cursor < len(self.elements) + ) + ) + + + def size(self): + if self.singleElement is not None: + return 1 + + if self.elements is not None: + return len(self.elements) + + return 0 + + __len__ = size + + + def getDescription(self): + """Deprecated. Directly access elementDescription attribute""" + + return self.elementDescription + + +class RewriteRuleTokenStream(RewriteRuleElementStream): + """@brief Internal helper class.""" + + def toTree(self, el): + # Don't convert to a tree unless they explicitly call nextTree. + # This way we can do hetero tree nodes in rewrite. + return el + + + def nextNode(self): + t = self._next() + return self.adaptor.createWithPayload(t) + + + def nextToken(self): + return self._next() + + + def dup(self, el): + raise TypeError("dup can't be called for a token stream.") + + +class RewriteRuleSubtreeStream(RewriteRuleElementStream): + """@brief Internal helper class.""" + + def nextNode(self): + """ + Treat next element as a single node even if it's a subtree. + This is used instead of next() when the result has to be a + tree root node. Also prevents us from duplicating recently-added + children; e.g., ^(type ID)+ adds ID to type and then 2nd iteration + must dup the type node, but ID has been added. + + Referencing a rule result twice is ok; dup entire tree as + we can't be adding trees as root; e.g., expr expr. + + Hideous code duplication here with super.next(). Can't think of + a proper way to refactor. This needs to always call dup node + and super.next() doesn't know which to call: dup node or dup tree. + """ + + if (self.dirty + or (self.cursor >= len(self) and len(self) == 1) + ): + # if out of elements and size is 1, dup (at most a single node + # since this is for making root nodes). + el = self._next() + return self.adaptor.dupNode(el) + + # test size above then fetch + el = self._next() + return el + + + def dup(self, el): + return self.adaptor.dupTree(el) + + + +class RewriteRuleNodeStream(RewriteRuleElementStream): + """ + Queues up nodes matched on left side of -> in a tree parser. This is + the analog of RewriteRuleTokenStream for normal parsers. + """ + + def nextNode(self): + return self._next() + + + def toTree(self, el): + return self.adaptor.dupNode(el) + + + def dup(self, el): + # we dup every node, so don't have to worry about calling dup; short- + #circuited next() so it doesn't call. + raise TypeError("dup can't be called for a node stream.") + + +class TreeRuleReturnScope(RuleReturnScope): + """ + This is identical to the ParserRuleReturnScope except that + the start property is a tree nodes not Token object + when you are parsing trees. To be generic the tree node types + have to be Object. + """ + + def __init__(self): + self.start = None + self.tree = None + + + def getStart(self): + return self.start + + + def getTree(self): + return self.tree + diff --git a/google_appengine/lib/antlr3/antlr3/treewizard.py b/google_appengine/lib/antlr3/antlr3/treewizard.py new file mode 100755 index 0000000..a55274c --- /dev/null +++ b/google_appengine/lib/antlr3/antlr3/treewizard.py @@ -0,0 +1,612 @@ +""" @package antlr3.tree +@brief ANTLR3 runtime package, treewizard module + +A utility module to create ASTs at runtime. +See for an overview. Note that the API of the Python implementation is slightly different. + +""" + +# begin[licence] +# +# [The "BSD licence"] +# Copyright (c) 2005-2008 Terence Parr +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# end[licence] + +from antlr3.constants import INVALID_TOKEN_TYPE +from antlr3.tokens import CommonToken +from antlr3.tree import CommonTree, CommonTreeAdaptor + + +def computeTokenTypes(tokenNames): + """ + Compute a dict that is an inverted index of + tokenNames (which maps int token types to names). + """ + + if tokenNames is None: + return {} + + return dict((name, type) for type, name in enumerate(tokenNames)) + + +## token types for pattern parser +EOF = -1 +BEGIN = 1 +END = 2 +ID = 3 +ARG = 4 +PERCENT = 5 +COLON = 6 +DOT = 7 + +class TreePatternLexer(object): + def __init__(self, pattern): + ## The tree pattern to lex like "(A B C)" + self.pattern = pattern + + ## Index into input string + self.p = -1 + + ## Current char + self.c = None + + ## How long is the pattern in char? + self.n = len(pattern) + + ## Set when token type is ID or ARG + self.sval = None + + self.error = False + + self.consume() + + + __idStartChar = frozenset( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' + ) + __idChar = __idStartChar | frozenset('0123456789') + + def nextToken(self): + self.sval = "" + while self.c != EOF: + if self.c in (' ', '\n', '\r', '\t'): + self.consume() + continue + + if self.c in self.__idStartChar: + self.sval += self.c + self.consume() + while self.c in self.__idChar: + self.sval += self.c + self.consume() + + return ID + + if self.c == '(': + self.consume() + return BEGIN + + if self.c == ')': + self.consume() + return END + + if self.c == '%': + self.consume() + return PERCENT + + if self.c == ':': + self.consume() + return COLON + + if self.c == '.': + self.consume() + return DOT + + if self.c == '[': # grab [x] as a string, returning x + self.consume() + while self.c != ']': + if self.c == '\\': + self.consume() + if self.c != ']': + self.sval += '\\' + + self.sval += self.c + + else: + self.sval += self.c + + self.consume() + + self.consume() + return ARG + + self.consume() + self.error = True + return EOF + + return EOF + + + def consume(self): + self.p += 1 + if self.p >= self.n: + self.c = EOF + + else: + self.c = self.pattern[self.p] + + +class TreePatternParser(object): + def __init__(self, tokenizer, wizard, adaptor): + self.tokenizer = tokenizer + self.wizard = wizard + self.adaptor = adaptor + self.ttype = tokenizer.nextToken() # kickstart + + + def pattern(self): + if self.ttype == BEGIN: + return self.parseTree() + + elif self.ttype == ID: + node = self.parseNode() + if self.ttype == EOF: + return node + + return None # extra junk on end + + return None + + + def parseTree(self): + if self.ttype != BEGIN: + return None + + self.ttype = self.tokenizer.nextToken() + root = self.parseNode() + if root is None: + return None + + while self.ttype in (BEGIN, ID, PERCENT, DOT): + if self.ttype == BEGIN: + subtree = self.parseTree() + self.adaptor.addChild(root, subtree) + + else: + child = self.parseNode() + if child is None: + return None + + self.adaptor.addChild(root, child) + + if self.ttype != END: + return None + + self.ttype = self.tokenizer.nextToken() + return root + + + def parseNode(self): + # "%label:" prefix + label = None + + if self.ttype == PERCENT: + self.ttype = self.tokenizer.nextToken() + if self.ttype != ID: + return None + + label = self.tokenizer.sval + self.ttype = self.tokenizer.nextToken() + if self.ttype != COLON: + return None + + self.ttype = self.tokenizer.nextToken() # move to ID following colon + + # Wildcard? + if self.ttype == DOT: + self.ttype = self.tokenizer.nextToken() + wildcardPayload = CommonToken(0, ".") + node = WildcardTreePattern(wildcardPayload) + if label is not None: + node.label = label + return node + + # "ID" or "ID[arg]" + if self.ttype != ID: + return None + + tokenName = self.tokenizer.sval + self.ttype = self.tokenizer.nextToken() + + if tokenName == "nil": + return self.adaptor.nil() + + text = tokenName + # check for arg + arg = None + if self.ttype == ARG: + arg = self.tokenizer.sval + text = arg + self.ttype = self.tokenizer.nextToken() + + # create node + treeNodeType = self.wizard.getTokenType(tokenName) + if treeNodeType == INVALID_TOKEN_TYPE: + return None + + node = self.adaptor.createFromType(treeNodeType, text) + if label is not None and isinstance(node, TreePattern): + node.label = label + + if arg is not None and isinstance(node, TreePattern): + node.hasTextArg = True + + return node + + +class TreePattern(CommonTree): + """ + When using %label:TOKENNAME in a tree for parse(), we must + track the label. + """ + + def __init__(self, payload): + CommonTree.__init__(self, payload) + + self.label = None + self.hasTextArg = None + + + def toString(self): + if self.label is not None: + return '%' + self.label + ':' + CommonTree.toString(self) + + else: + return CommonTree.toString(self) + + +class WildcardTreePattern(TreePattern): + pass + + +class TreePatternTreeAdaptor(CommonTreeAdaptor): + """This adaptor creates TreePattern objects for use during scan()""" + + def createWithPayload(self, payload): + return TreePattern(payload) + + +class TreeWizard(object): + """ + Build and navigate trees with this object. Must know about the names + of tokens so you have to pass in a map or array of token names (from which + this class can build the map). I.e., Token DECL means nothing unless the + class can translate it to a token type. + + In order to create nodes and navigate, this class needs a TreeAdaptor. + + This class can build a token type -> node index for repeated use or for + iterating over the various nodes with a particular type. + + This class works in conjunction with the TreeAdaptor rather than moving + all this functionality into the adaptor. An adaptor helps build and + navigate trees using methods. This class helps you do it with string + patterns like "(A B C)". You can create a tree from that pattern or + match subtrees against it. + """ + + def __init__(self, adaptor=None, tokenNames=None, typeMap=None): + self.adaptor = adaptor + if typeMap is None: + self.tokenNameToTypeMap = computeTokenTypes(tokenNames) + + else: + if tokenNames is not None: + raise ValueError("Can't have both tokenNames and typeMap") + + self.tokenNameToTypeMap = typeMap + + + def getTokenType(self, tokenName): + """Using the map of token names to token types, return the type.""" + + try: + return self.tokenNameToTypeMap[tokenName] + except KeyError: + return INVALID_TOKEN_TYPE + + + def create(self, pattern): + """ + Create a tree or node from the indicated tree pattern that closely + follows ANTLR tree grammar tree element syntax: + + (root child1 ... child2). + + You can also just pass in a node: ID + + Any node can have a text argument: ID[foo] + (notice there are no quotes around foo--it's clear it's a string). + + nil is a special name meaning "give me a nil node". Useful for + making lists: (nil A B C) is a list of A B C. + """ + + tokenizer = TreePatternLexer(pattern) + parser = TreePatternParser(tokenizer, self, self.adaptor) + return parser.pattern() + + + def index(self, tree): + """Walk the entire tree and make a node name to nodes mapping. + + For now, use recursion but later nonrecursive version may be + more efficient. Returns a dict int -> list where the list is + of your AST node type. The int is the token type of the node. + """ + + m = {} + self._index(tree, m) + return m + + + def _index(self, t, m): + """Do the work for index""" + + if t is None: + return + + ttype = self.adaptor.getType(t) + elements = m.get(ttype) + if elements is None: + m[ttype] = elements = [] + + elements.append(t) + for i in range(self.adaptor.getChildCount(t)): + child = self.adaptor.getChild(t, i) + self._index(child, m) + + + def find(self, tree, what): + """Return a list of matching token. + + what may either be an integer specifzing the token type to find or + a string with a pattern that must be matched. + + """ + + if isinstance(what, (int, long)): + return self._findTokenType(tree, what) + + elif isinstance(what, basestring): + return self._findPattern(tree, what) + + else: + raise TypeError("'what' must be string or integer") + + + def _findTokenType(self, t, ttype): + """Return a List of tree nodes with token type ttype""" + + nodes = [] + + def visitor(tree, parent, childIndex, labels): + nodes.append(tree) + + self.visit(t, ttype, visitor) + + return nodes + + + def _findPattern(self, t, pattern): + """Return a List of subtrees matching pattern.""" + + subtrees = [] + + # Create a TreePattern from the pattern + tokenizer = TreePatternLexer(pattern) + parser = TreePatternParser(tokenizer, self, TreePatternTreeAdaptor()) + tpattern = parser.pattern() + + # don't allow invalid patterns + if (tpattern is None or tpattern.isNil() + or isinstance(tpattern, WildcardTreePattern)): + return None + + rootTokenType = tpattern.getType() + + def visitor(tree, parent, childIndex, label): + if self._parse(tree, tpattern, None): + subtrees.append(tree) + + self.visit(t, rootTokenType, visitor) + + return subtrees + + + def visit(self, tree, what, visitor): + """Visit every node in tree matching what, invoking the visitor. + + If what is a string, it is parsed as a pattern and only matching + subtrees will be visited. + The implementation uses the root node of the pattern in combination + with visit(t, ttype, visitor) so nil-rooted patterns are not allowed. + Patterns with wildcard roots are also not allowed. + + If what is an integer, it is used as a token type and visit will match + all nodes of that type (this is faster than the pattern match). + The labels arg of the visitor action method is never set (it's None) + since using a token type rather than a pattern doesn't let us set a + label. + """ + + if isinstance(what, (int, long)): + self._visitType(tree, None, 0, what, visitor) + + elif isinstance(what, basestring): + self._visitPattern(tree, what, visitor) + + else: + raise TypeError("'what' must be string or integer") + + + def _visitType(self, t, parent, childIndex, ttype, visitor): + """Do the recursive work for visit""" + + if t is None: + return + + if self.adaptor.getType(t) == ttype: + visitor(t, parent, childIndex, None) + + for i in range(self.adaptor.getChildCount(t)): + child = self.adaptor.getChild(t, i) + self._visitType(child, t, i, ttype, visitor) + + + def _visitPattern(self, tree, pattern, visitor): + """ + For all subtrees that match the pattern, execute the visit action. + """ + + # Create a TreePattern from the pattern + tokenizer = TreePatternLexer(pattern) + parser = TreePatternParser(tokenizer, self, TreePatternTreeAdaptor()) + tpattern = parser.pattern() + + # don't allow invalid patterns + if (tpattern is None or tpattern.isNil() + or isinstance(tpattern, WildcardTreePattern)): + return + + rootTokenType = tpattern.getType() + + def rootvisitor(tree, parent, childIndex, labels): + labels = {} + if self._parse(tree, tpattern, labels): + visitor(tree, parent, childIndex, labels) + + self.visit(tree, rootTokenType, rootvisitor) + + + def parse(self, t, pattern, labels=None): + """ + Given a pattern like (ASSIGN %lhs:ID %rhs:.) with optional labels + on the various nodes and '.' (dot) as the node/subtree wildcard, + return true if the pattern matches and fill the labels Map with + the labels pointing at the appropriate nodes. Return false if + the pattern is malformed or the tree does not match. + + If a node specifies a text arg in pattern, then that must match + for that node in t. + """ + + tokenizer = TreePatternLexer(pattern) + parser = TreePatternParser(tokenizer, self, TreePatternTreeAdaptor()) + tpattern = parser.pattern() + + return self._parse(t, tpattern, labels) + + + def _parse(self, t1, t2, labels): + """ + Do the work for parse. Check to see if the t2 pattern fits the + structure and token types in t1. Check text if the pattern has + text arguments on nodes. Fill labels map with pointers to nodes + in tree matched against nodes in pattern with labels. + """ + + # make sure both are non-null + if t1 is None or t2 is None: + return False + + # check roots (wildcard matches anything) + if not isinstance(t2, WildcardTreePattern): + if self.adaptor.getType(t1) != t2.getType(): + return False + + if t2.hasTextArg and self.adaptor.getText(t1) != t2.getText(): + return False + + if t2.label is not None and labels is not None: + # map label in pattern to node in t1 + labels[t2.label] = t1 + + # check children + n1 = self.adaptor.getChildCount(t1) + n2 = t2.getChildCount() + if n1 != n2: + return False + + for i in range(n1): + child1 = self.adaptor.getChild(t1, i) + child2 = t2.getChild(i) + if not self._parse(child1, child2, labels): + return False + + return True + + + def equals(self, t1, t2, adaptor=None): + """ + Compare t1 and t2; return true if token types/text, structure match + exactly. + The trees are examined in their entirety so that (A B) does not match + (A B C) nor (A (B C)). + """ + + if adaptor is None: + adaptor = self.adaptor + + return self._equals(t1, t2, adaptor) + + + def _equals(self, t1, t2, adaptor): + # make sure both are non-null + if t1 is None or t2 is None: + return False + + # check roots + if adaptor.getType(t1) != adaptor.getType(t2): + return False + + if adaptor.getText(t1) != adaptor.getText(t2): + return False + + # check children + n1 = adaptor.getChildCount(t1) + n2 = adaptor.getChildCount(t2) + if n1 != n2: + return False + + for i in range(n1): + child1 = adaptor.getChild(t1, i) + child2 = adaptor.getChild(t2, i) + if not self._equals(child1, child2, adaptor): + return False + + return True diff --git a/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/PKG-INFO b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/PKG-INFO new file mode 100755 index 0000000..2fef3b3 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 1.0 +Name: antlr-python-runtime +Version: 3.1.1 +Summary: Runtime package for ANTLR3 +Home-page: http://www.antlr.org/ +Author: Benjamin Niemann +Author-email: pink@odahoda.de +License: BSD +Download-URL: http://www.antlr.org/download.html +Description: This is the runtime package for ANTLR3, which is required to use parsers + generated by ANTLR3. + +Platform: UNKNOWN diff --git a/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/SOURCES.txt b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/SOURCES.txt new file mode 100644 index 0000000..ccb2ff9 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/SOURCES.txt @@ -0,0 +1,23 @@ +AUTHORS +LICENSE +MANIFEST.in +README +ez_setup.py +setup.py +antlr3/__init__.py +antlr3/compat.py +antlr3/constants.py +antlr3/dfa.py +antlr3/dottreegen.py +antlr3/exceptions.py +antlr3/extras.py +antlr3/main.py +antlr3/recognizers.py +antlr3/streams.py +antlr3/tokens.py +antlr3/tree.py +antlr3/treewizard.py +antlr_python_runtime.egg-info/PKG-INFO +antlr_python_runtime.egg-info/SOURCES.txt +antlr_python_runtime.egg-info/dependency_links.txt +antlr_python_runtime.egg-info/top_level.txt diff --git a/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/dependency_links.txt b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/top_level.txt b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/top_level.txt new file mode 100644 index 0000000..a6ea000 --- /dev/null +++ b/google_appengine/lib/antlr3/antlr_python_runtime.egg-info/top_level.txt @@ -0,0 +1 @@ +antlr3 diff --git a/google_appengine/lib/antlr3/setup.py b/google_appengine/lib/antlr3/setup.py new file mode 100755 index 0000000..078deef --- /dev/null +++ b/google_appengine/lib/antlr3/setup.py @@ -0,0 +1,289 @@ +# bootstrapping setuptools +import ez_setup +ez_setup.use_setuptools() + +import os +import sys +import textwrap +from distutils.errors import * +from distutils.command.clean import clean as _clean +from distutils.cmd import Command +from setuptools import setup +from distutils import log + +from distutils.core import setup + + +class clean(_clean): + """Also cleanup local temp files.""" + + def run(self): + _clean.run(self) + + import fnmatch + + # kill temporary files + patterns = [ + # generic tempfiles + '*~', '*.bak', '*.pyc', + + # tempfiles generated by ANTLR runs + 't[0-9]*Lexer.py', 't[0-9]*Parser.py', + '*.tokens', '*__.g', + ] + + for path in ('antlr3', 'unittests', 'tests'): + path = os.path.join(os.path.dirname(__file__), path) + if os.path.isdir(path): + for root, dirs, files in os.walk(path, topdown=True): + graveyard = [] + for pat in patterns: + graveyard.extend(fnmatch.filter(files, pat)) + + for name in graveyard: + filePath = os.path.join(root, name) + + try: + log.info("removing '%s'", filePath) + os.unlink(filePath) + except OSError, exc: + log.warn( + "Failed to delete '%s': %s", + filePath, exc + ) + + +class TestError(DistutilsError): + pass + + +# grml.. the class name appears in the --help output: +# ... +# Options for 'CmdUnitTest' command +# ... +# so I have to use a rather ugly name... +class unittest(Command): + """Run unit tests for package""" + + description = "run unit tests for package" + + user_options = [ + ] + boolean_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + testDir = os.path.join(os.path.dirname(__file__), 'unittests') + if not os.path.isdir(testDir): + raise DistutilsFileError( + "There is not 'unittests' directory. Did you fetch the " + "development version?", + ) + + import glob + import imp + import unittest + import traceback + import StringIO + + suite = unittest.TestSuite() + loadFailures = [] + + # collect tests from all unittests/test*.py files + testFiles = [] + for testPath in glob.glob(os.path.join(testDir, 'test*.py')): + testFiles.append(testPath) + + testFiles.sort() + for testPath in testFiles: + testID = os.path.basename(testPath)[:-3] + + try: + modFile, modPathname, modDescription \ + = imp.find_module(testID, [testDir]) + + testMod = imp.load_module( + testID, modFile, modPathname, modDescription + ) + + suite.addTests( + unittest.defaultTestLoader.loadTestsFromModule(testMod) + ) + + except Exception: + buf = StringIO.StringIO() + traceback.print_exc(file=buf) + + loadFailures.append( + (os.path.basename(testPath), buf.getvalue()) + ) + + + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + for testName, error in loadFailures: + sys.stderr.write('\n' + '='*70 + '\n') + sys.stderr.write( + "Failed to load test module %s\n" % testName + ) + sys.stderr.write(error) + sys.stderr.write('\n') + + if not result.wasSuccessful() or loadFailures: + raise TestError( + "Unit test suite failed!", + ) + + +class functest(Command): + """Run functional tests for package""" + + description = "run functional tests for package" + + user_options = [ + ('testcase=', None, + "testcase to run [default: run all]"), + ('antlr-version=', None, + "ANTLR version to use [default: HEAD (in ../../build)]"), + ] + + boolean_options = [] + + def initialize_options(self): + self.testcase = None + self.antlr_version = 'HEAD' + + + def finalize_options(self): + pass + + + def run(self): + import glob + import imp + import unittest + import traceback + import StringIO + + testDir = os.path.join(os.path.dirname(__file__), 'tests') + if not os.path.isdir(testDir): + raise DistutilsFileError( + "There is not 'tests' directory. Did you fetch the " + "development version?", + ) + + # make sure, relative imports from testcases work + sys.path.insert(0, testDir) + + rootDir = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..')) + + if self.antlr_version == 'HEAD': + classpath = [ + os.path.join(rootDir, 'build', 'classes'), + os.path.join(rootDir, 'build', 'rtclasses') + ] + else: + classpath = [ + os.path.join(rootDir, 'archive', + 'antlr-%s.jar' % self.antlr_version) + ] + + classpath.extend([ + os.path.join(rootDir, 'lib', 'antlr-2.7.7.jar'), + os.path.join(rootDir, 'lib', 'stringtemplate-3.2.jar'), + os.path.join(rootDir, 'lib', 'junit-4.2.jar') + ]) + os.environ['CLASSPATH'] = ':'.join(classpath) + + os.environ['ANTLRVERSION'] = self.antlr_version + + suite = unittest.TestSuite() + loadFailures = [] + + # collect tests from all tests/t*.py files + testFiles = [] + for testPath in glob.glob(os.path.join(testDir, 't*.py')): + if (testPath.endswith('Lexer.py') + or testPath.endswith('Parser.py') + ): + continue + + # if a single testcase has been selected, filter out all other + # tests + if (self.testcase is not None + and os.path.basename(testPath)[:-3] != self.testcase + ): + continue + + testFiles.append(testPath) + + testFiles.sort() + for testPath in testFiles: + testID = os.path.basename(testPath)[:-3] + + try: + modFile, modPathname, modDescription \ + = imp.find_module(testID, [testDir]) + + testMod = imp.load_module( + testID, modFile, modPathname, modDescription + ) + + suite.addTests( + unittest.defaultTestLoader.loadTestsFromModule(testMod) + ) + + except Exception: + buf = StringIO.StringIO() + traceback.print_exc(file=buf) + + loadFailures.append( + (os.path.basename(testPath), buf.getvalue()) + ) + + + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + for testName, error in loadFailures: + sys.stderr.write('\n' + '='*70 + '\n') + sys.stderr.write( + "Failed to load test module %s\n" % testName + ) + sys.stderr.write(error) + sys.stderr.write('\n') + + if not result.wasSuccessful() or loadFailures: + raise TestError( + "Functional test suite failed!", + ) + + +setup(name='antlr_python_runtime', + version='3.1.1', + packages=['antlr3'], + + author="Benjamin Niemann", + author_email="pink@odahoda.de", + url="http://www.antlr.org/", + download_url="http://www.antlr.org/download.html", + license="BSD", + description="Runtime package for ANTLR3", + long_description=textwrap.dedent('''\ + This is the runtime package for ANTLR3, which is required to use parsers + generated by ANTLR3. + '''), + + + cmdclass={'unittest': unittest, + 'functest': functest, + 'clean': clean + }, + ) diff --git a/google_appengine/lib/cacerts/cacerts.txt b/google_appengine/lib/cacerts/cacerts.txt new file mode 100644 index 0000000..374fd37 --- /dev/null +++ b/google_appengine/lib/cacerts/cacerts.txt @@ -0,0 +1,601 @@ +# Certifcate Authority certificates for validating SSL connections. +# +# This file contains PEM format certificates generated from +# http://mxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is the Netscape security libraries. +# +# The Initial Developer of the Original Code is +# Netscape Communications Corporation. +# Portions created by the Initial Developer are Copyright (C) 1994-2000 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +Verisign/RSA Secure Server CA +============================= + +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0 +MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV +BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy +dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ +ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII +0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI +uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI +hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3 +YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc +1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- + +Thawte Personal Basic CA +======================== + +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFBlcnNvbmFsIEJhc2lj +IENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNpY0B0aGF3dGUuY29tMB4X +DTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgcsxCzAJBgNVBAYTAlpBMRUw +EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE +ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy +dmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBD +QTEoMCYGCSqGSIb3DQEJARYZcGVyc29uYWwtYmFzaWNAdGhhd3RlLmNvbTCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLyTU23AUE+CFeZIlDWmWr5vQvoPR+53 +dXLdjUmbllegeNTKP1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJl+A1OFdK +wPQIcOk8RHtQfmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7 +G1sY0b8jkyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQF +AAOBgQAt4plrsD16iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7 +c8a914phXAPjLSeoF+CEhULcXpvGt7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95B21P +9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN92NWod8isQ== +-----END CERTIFICATE----- + +Thawte Personal Premium CA +========================== + +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMaVGhhd3RlIFBlcnNvbmFsIFByZW1p +dW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXByZW1pdW1AdGhhd3RlLmNv +bTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5NTlaMIHPMQswCQYDVQQGEwJa +QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xGjAY +BgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9u +IFNlcnZpY2VzIERpdmlzaW9uMSMwIQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJl +bWl1bSBDQTEqMCgGCSqGSIb3DQEJARYbcGVyc29uYWwtcHJlbWl1bUB0aGF3dGUu +Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJZtn4B0TPuYwu8KHvE0Vs +Bd/eJxZRNkERbGw77f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ8/JE2dWI +Et12TfIa/G8jHnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYD +ZicRFTuqW/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBAUAA4GBAGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIH +b4Vnjt4rueIXsDqg8A6iAJrf8xQVbrvIhVqYgPn/vnQdPfP+MCXRNzRn+qVxeTBh +KXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+WjS9Q2wfD6h+rM+D1KzGJ +-----END CERTIFICATE----- + +Thawte Personal Freemail CA +=========================== + +-----BEGIN CERTIFICATE----- +MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVt +YWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUu +Y29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEa +MBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBG +cmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhh +d3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfY +DFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5E +rHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVq +uzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zAN +BgkqhkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP +MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa +/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr5PjRznei +gQ== +-----END CERTIFICATE----- + +Thawte Server CA +================ + +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== + +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Equifax Secure CA +================= + +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 +MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx +dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f +BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A +cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ +MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw +ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj +IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh +1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority +======================================================= + +-----BEGIN CERTIFICATE----- +MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh +c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05 +NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD +VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp +bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N +H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR +4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN +BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo +EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5 +FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx +lA== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority +======================================================= + +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyh +YGt+eSz6Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7 +FYCTXOvnzAhsPz6zSvz/S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBAIobK/o5wXTXXtgZZKJYSi034DNHD6zt96rbHuSLBlxg +J8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUYYAS/QoD90KioHgE796Nc +r6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2lw0Xd8rY +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= + +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G2 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK +VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm +Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J +h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul +uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68 +DzFc6PLZ +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G2 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns +YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y +aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe +Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj +IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx +KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM +HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw +DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC +AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji +nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX +rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn +jBJ7xUS0rg== +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G2 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgNCBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4xBewRNzjMHPVKmIquNDM +HO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDHqGKB3FtK +qsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwj +cSGIL4LcY/oCRaxFWdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0y +cyfYaT5DdPauxYma51N86Xv2S/PBZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRP +T8qAkbYp +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G3 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4 +nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO +8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV +ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb +PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2 +6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr +n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a +qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4 +wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 +ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs +pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4 +E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G3 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy +aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp +Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV +BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp +Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g +Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU +J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO +JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY +wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o +koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN +qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E +Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe +xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u +7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU +sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI +sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP +cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 +GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ ++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd +U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm +NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY +ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ +ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 +CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq +g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c +2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ +bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== + +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= + +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 2 +============================= + +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj +dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0 +NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD +VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G +vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/ +BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX +MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl +IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw +NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq +y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy +0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1 +E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN +-----END CERTIFICATE----- + +Thawte Time Stamping CA +======================= + +-----BEGIN CERTIFICATE----- +MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN +BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd +BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN +MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g +Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG +A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l +c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT +6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa +Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL +8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC +9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ +pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ +CayJSdM= +-----END CERTIFICATE----- + +thawte Primary Root CA +====================== + +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +VeriSign Class 3 Public Primary Certification Authority - G5 +============================================================ + +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- diff --git a/google_appengine/lib/django/AUTHORS b/google_appengine/lib/django/AUTHORS new file mode 100644 index 0000000..a2cf8c6 --- /dev/null +++ b/google_appengine/lib/django/AUTHORS @@ -0,0 +1,216 @@ +Django was originally created in late 2003 at World Online, the Web division +of the Lawrence Journal-World newspaper in Lawrence, Kansas. + +The PRIMARY AUTHORS are (and/or have been): + +Adrian Holovaty , who originally created Django with +Simon and currently oversees things with Jacob. + +Simon Willison , who originally created Django with +Adrian during his year-long internship/placement at World Online and currently +helps from the sidelines. + +Jacob Kaplan-Moss , who joined the team shortly +before Simon departed and currently oversees things with Adrian. + +Wilson Miner , who designed Django's admin +interface, pretty error pages, official Web site (djangoproject.com) and has +made many other contributions. He makes us look good. + +Malcolm Tredinnick , who has made +significant contributions to all levels of the framework, from its database +layer to template system and documentation. + +Georg "Hugo" Bauer , who added +internationalization support, manages i18n contributions and has made a ton +of excellent tweaks, feature additions and bug fixes. + +Luke Plant , who has contributed many excellent +improvements, including database-level improvements, the CSRF middleware and +unit tests. + +Russell Keith-Magee , who has contributed many excellent +improvements, including refactoring of the Django ORM code and unit tests. + +Robert Wittams , who majorly refactored the Django +admin application to allow for easier reuse and has made a ton of excellent +tweaks, feature additions and bug fixes. + + +And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- +people who have submitted patches, reported bugs, added translations, helped +answer newbie questions, and generally made Django that much better: + + adurdin@gmail.com + Andreas + andy@jadedplanet.net + ant9000@netwise.it + David Ascher + Arthur + Jiri Barton + Ned Batchelder + Shannon -jj Behrens + Esdras Beleza + James Bennett + Ben + Paul Bissex + Simon Blanchard + Andrew Brehaut + brut.alll@gmail.com + Jonathan Buchanan + Antonio Cavedoni + C8E + Chris Chamberlin + Amit Chakradeo + ChaosKCW + Ian Clelland + crankycoder@gmail.com + Matt Croydon + Jure Cuhalev + dackze+django@gmail.com + Dirk Datzert + Jonathan Daugherty (cygnus) + dave@thebarproject.com + Jason Davies (Esaj) + Alex Dedul + deric@monowerks.com + dne@mayonnaise.net + Maximillian Dornseif + Jeremy Dunck + Andy Dustman + Clint Ecker + Enrico + Ludvig Ericson + Dirk Eschler + Marc Fargas + favo@exoweb.net + Eric Floehr + Jorge Gajon + gandalf@owca.info + Baishampayan Ghose + martin.glueck@gmail.com + Simon Greenhill + Owen Griffiths + Espen Grindhaug + Brian Harring + Brant Harris + Hawkeye + Joe Heck + Joel Heenan + hipertracker@gmail.com + Ian Holsman + Kieran Holland + Robert Rock Howard + Jason Huggins + Tom Insam + Baurzhan Ismagulov + jcrasta@gmail.com + Michael Josephson + jpellerin@gmail.com + junzhang.jn@gmail.com + Antti Kaihola + Ben Dean Kawamura + Garth Kidd + kilian + Sune Kirkeby + Bastian Kleineidam + Cameron Knight (ckknight) + Meir Kriheli + Bruce Kroeze + Joseph Kocherhans + konrad@gwu.edu + lakin.wecker@gmail.com + Stuart Langridge + Nicola Larosa + Eugene Lazutkin + Jeong-Min Lee + Christopher Lenz + lerouxb@gmail.com + Waylan Limberg + limodou + mattmcc + Martin Maney + masonsimon+django@gmail.com + Manuzhai + Petar Marić + Nuno Mariz + mark@junklight.com + Yasushi Masuda + mattycakes@gmail.com + Jason McBrayer + mccutchen@gmail.com + michael.mcewan@gmail.com + mikko@sorl.net + mitakummaa@gmail.com + mmarshall + Eric Moritz + Robin Munn + Robert Myers + Nebojša Dorđević + Fraser Nevett + Sam Newman + Neal Norwitz + oggie rob + Jay Parlar + pavithran s + pgross@thoughtworks.com + phaedo + phil@produxion.net + phil.h.smith@gmail.com + Gustavo Picon + Luke Plant + plisk + Daniel Poelzleithner + J. Rademaker + Michael Radziej + ramiro + Brian Ray + remco@diji.biz + rhettg@gmail.com + Oliver Rutherfurd + Ivan Sagalaev (Maniac) + David Schein + scott@staplefish.com + serbaut@gmail.com + Pete Shinners + SmileyChris + smurf@smurf.noris.de + sopel + Georgi Stanojevski + Thomas Steinacher + nowell strite + Radek Švarz + Swaroop C H + Aaron Swartz + Tyson Tate + Tom Tobin + Joe Topjian + torne-django@wolfpuppy.org.uk + Karen Tracey + Makoto Tsuyuki + Amit Upadhyay + Geert Vanderkelen + viestards.lists@gmail.com + Milton Waddams + wam-djangobug@wamber.net + Dan Watson + Chris Wesseling + Rachel Willmer + Gary Wilson + wojtek + ye7cakf02@sneakemail.com + ymasuda@ethercube.com + Cheng Zhang + +A big THANK YOU goes to: + + Rob Curley and Ralph Gage for letting us open-source Django. + + Frank Wiles for making excellent arguments for open-sourcing, and for + his sage sysadmin advice. + + Ian Bicking for convincing Adrian to ditch code generation. + + Mark Pilgrim for diveintopython.org. + + Guido van Rossum for creating Python. diff --git a/google_appengine/lib/django/INSTALL b/google_appengine/lib/django/INSTALL new file mode 100644 index 0000000..23e24c0 --- /dev/null +++ b/google_appengine/lib/django/INSTALL @@ -0,0 +1,22 @@ +Thanks for downloading Django. + +To install it, make sure you have Python 2.3 or greater installed. Then run +this command from the command prompt: + + python setup.py install + +Note this requires a working Internet connection if you don't already have the +Python utility "setuptools" installed. + +AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's +site-packages directory, which is located wherever your Python installation +lives. Some places you might check are: + + /usr/lib/python2.4/site-packages (Unix, Python 2.4) + /usr/lib/python2.3/site-packages (Unix, Python 2.3) + C:\\PYTHON\site-packages (Windows) + +This second solution does not require a working Internet connection; it +bypasses "setuptools" entirely. + +For more detailed instructions, see docs/install.txt. diff --git a/google_appengine/lib/django/LICENSE b/google_appengine/lib/django/LICENSE new file mode 100644 index 0000000..ba3e68a --- /dev/null +++ b/google_appengine/lib/django/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2005, the Lawrence Journal-World +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/google_appengine/lib/django/PKG-INFO b/google_appengine/lib/django/PKG-INFO new file mode 100644 index 0000000..907ba85 --- /dev/null +++ b/google_appengine/lib/django/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.0 +Name: Django +Version: 0.96.4 +Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. +Home-page: http://www.djangoproject.com/ +Author: Django Software Foundation +Author-email: foundation@djangoproject.com +License: UNKNOWN +Download-URL: http://media.djangoproject.com/releases/0.96/Django-0.96.4.tar.gz +Description: UNKNOWN +Platform: UNKNOWN diff --git a/google_appengine/lib/django/README b/google_appengine/lib/django/README new file mode 100644 index 0000000..084f863 --- /dev/null +++ b/google_appengine/lib/django/README @@ -0,0 +1,37 @@ +Django is a high-level Python Web framework that encourages rapid development +and clean, pragmatic design. + +All documentation is in the "docs" directory and online at +http://www.djangoproject.com/documentation/. If you're just getting started, +here's how we recommend you read the docs: + + * First, read docs/install.txt for instructions on installing Django. + + * Next, work through the tutorials in order (docs/tutorial01.txt, + docs/tutorial02.txt, etc.). + + * If you want to set up an actual deployment server, read docs/modpython.txt + for instructions on running Django under mod_python. + + * The rest of the documentation is of the reference-manual variety. + Read it -- and the FAQ -- as you run into problems. + +Docs are updated rigorously. If you find any problems in the docs, or think they +should be clarified in any way, please take 30 seconds to fill out a ticket +here: + +http://code.djangoproject.com/newticket + +To get more help: + + * Join the #django channel on irc.freenode.net. Lots of helpful people + hang out there. Read the archives at http://simon.bofh.ms/logger/django/ . + + * Join the django-users mailing list, or read the archives, at + http://groups.google.com/group/django-users. + +To contribute to Django: + + * Check out http://www.djangoproject.com/community/ for information + about getting involved. + diff --git a/google_appengine/lib/django/django/__init__.py b/google_appengine/lib/django/django/__init__.py new file mode 100755 index 0000000..35628b1 --- /dev/null +++ b/google_appengine/lib/django/django/__init__.py @@ -0,0 +1 @@ +VERSION = (0, 96.4, None) diff --git a/google_appengine/lib/django/django/bin/__init__.py b/google_appengine/lib/django/django/bin/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/google_appengine/lib/django/django/bin/compile-messages.py b/google_appengine/lib/django/django/bin/compile-messages.py new file mode 100755 index 0000000..f2193d3 --- /dev/null +++ b/google_appengine/lib/django/django/bin/compile-messages.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import optparse +import os +import sys + +def compile_messages(locale=None): + basedir = None + + if os.path.isdir(os.path.join('conf', 'locale')): + basedir = os.path.abspath(os.path.join('conf', 'locale')) + elif os.path.isdir('locale'): + basedir = os.path.abspath('locale') + else: + print "This script should be run from the Django SVN tree or your project or app tree." + sys.exit(1) + + if locale is not None: + basedir = os.path.join(basedir, locale, 'LC_MESSAGES') + + for dirpath, dirnames, filenames in os.walk(basedir): + for f in filenames: + if f.endswith('.po'): + sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) + pf = os.path.splitext(os.path.join(dirpath, f))[0] + # Store the names of the .mo and .po files in an environment + # variable, rather than doing a string replacement into the + # command, so that we can take advantage of shell quoting, to + # quote any malicious characters/escaping. + # See http://cyberelk.net/tim/articles/cmdline/ar01s02.html + os.environ['djangocompilemo'] = pf + '.mo' + os.environ['djangocompilepo'] = pf + '.po' + if sys.platform == 'win32': # Different shell-variable syntax + cmd = 'msgfmt -o "%djangocompilemo%" "%djangocompilepo%"' + else: + cmd = 'msgfmt -o "$djangocompilemo" "$djangocompilepo"' + os.system(cmd) + +def main(): + parser = optparse.OptionParser() + parser.add_option('-l', '--locale', dest='locale', + help="The locale to process. Default is to process all.") + options, args = parser.parse_args() + if len(args): + parser.error("This program takes no arguments") + compile_messages(options.locale) + +if __name__ == "__main__": + main() diff --git a/google_appengine/lib/django/django/bin/daily_cleanup.py b/google_appengine/lib/django/django/bin/daily_cleanup.py new file mode 100755 index 0000000..3b83583 --- /dev/null +++ b/google_appengine/lib/django/django/bin/daily_cleanup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +""" +Daily cleanup job. + +Can be run as a cronjob to clean out old data from the database (only expired +sessions at the moment). +""" + +from django.db import backend, connection, transaction + +def clean_up(): + # Clean up old database records + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \ + (backend.quote_name('django_session'), backend.quote_name('expire_date'))) + transaction.commit_unless_managed() + +if __name__ == "__main__": + clean_up() diff --git a/google_appengine/lib/django/django/bin/django-admin.py b/google_appengine/lib/django/django/bin/django-admin.py new file mode 100755 index 0000000..f518cdc --- /dev/null +++ b/google_appengine/lib/django/django/bin/django-admin.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +from django.core import management + +if __name__ == "__main__": + management.execute_from_command_line() diff --git a/google_appengine/lib/django/django/bin/make-messages.py b/google_appengine/lib/django/django/bin/make-messages.py new file mode 100755 index 0000000..34fb68d --- /dev/null +++ b/google_appengine/lib/django/django/bin/make-messages.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python + +# Need to ensure that the i18n framework is enabled +from django.conf import settings +settings.configure(USE_I18N = True) + +from django.utils.translation import templatize +import re +import os +import sys +import getopt + +pythonize_re = re.compile(r'\n\s*//') + +def make_messages(): + localedir = None + + if os.path.isdir(os.path.join('conf', 'locale')): + localedir = os.path.abspath(os.path.join('conf', 'locale')) + elif os.path.isdir('locale'): + localedir = os.path.abspath('locale') + else: + print "This script should be run from the django svn tree or your project or app tree." + print "If you did indeed run it from the svn checkout or your project or application," + print "maybe you are just missing the conf/locale (in the django tree) or locale (for project" + print "and application) directory?" + print "make-messages.py doesn't create it automatically, you have to create it by hand if" + print "you want to enable i18n for your project or application." + sys.exit(1) + + (opts, args) = getopt.getopt(sys.argv[1:], 'l:d:va') + + lang = None + domain = 'django' + verbose = False + all = False + + for o, v in opts: + if o == '-l': + lang = v + elif o == '-d': + domain = v + elif o == '-v': + verbose = True + elif o == '-a': + all = True + + if domain not in ('django', 'djangojs'): + print "currently make-messages.py only supports domains 'django' and 'djangojs'" + sys.exit(1) + if (lang is None and not all) or domain is None: + print "usage: make-messages.py -l " + print " or: make-messages.py -a" + sys.exit(1) + + languages = [] + + if lang is not None: + languages.append(lang) + elif all: + languages = [el for el in os.listdir(localedir) if not el.startswith('.')] + + for lang in languages: + + print "processing language", lang + basedir = os.path.join(localedir, lang, 'LC_MESSAGES') + if not os.path.isdir(basedir): + os.makedirs(basedir) + + pofile = os.path.join(basedir, '%s.po' % domain) + potfile = os.path.join(basedir, '%s.pot' % domain) + + if os.path.exists(potfile): + os.unlink(potfile) + + for (dirpath, dirnames, filenames) in os.walk("."): + for file in filenames: + if domain == 'djangojs' and file.endswith('.js'): + if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) + src = open(os.path.join(dirpath, file), "rb").read() + src = pythonize_re.sub('\n#', src) + open(os.path.join(dirpath, '%s.py' % file), "wb").write(src) + thefile = '%s.py' % file + cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy --from-code UTF-8 -o - "%s"' % ( + os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile)) + (stdin, stdout, stderr) = os.popen3(cmd, 'b') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running xgettext on %s" % file + print errors + sys.exit(8) + old = '#: '+os.path.join(dirpath, thefile)[2:] + new = '#: '+os.path.join(dirpath, file)[2:] + msgs = msgs.replace(old, new) + if msgs: + open(potfile, 'ab').write(msgs) + os.unlink(os.path.join(dirpath, thefile)) + elif domain == 'django' and (file.endswith('.py') or file.endswith('.html')): + thefile = file + if file.endswith('.html'): + src = open(os.path.join(dirpath, file), "rb").read() + open(os.path.join(dirpath, '%s.py' % file), "wb").write(templatize(src)) + thefile = '%s.py' % file + if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) + cmd = 'xgettext %s -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy --from-code UTF-8 -o - "%s"' % ( + os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile)) + (stdin, stdout, stderr) = os.popen3(cmd, 'b') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running xgettext on %s" % file + print errors + sys.exit(8) + if thefile != file: + old = '#: '+os.path.join(dirpath, thefile)[2:] + new = '#: '+os.path.join(dirpath, file)[2:] + msgs = msgs.replace(old, new) + if msgs: + open(potfile, 'ab').write(msgs) + if thefile != file: + os.unlink(os.path.join(dirpath, thefile)) + + if os.path.exists(potfile): + (stdin, stdout, stderr) = os.popen3('msguniq "%s"' % potfile, 'b') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running msguniq" + print errors + sys.exit(8) + open(potfile, 'w').write(msgs) + if os.path.exists(pofile): + (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 'b') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running msgmerge" + print errors + sys.exit(8) + open(pofile, 'wb').write(msgs) + os.unlink(potfile) + +if __name__ == "__main__": + make_messages() diff --git a/google_appengine/lib/django/django/bin/profiling/__init__.py b/google_appengine/lib/django/django/bin/profiling/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/google_appengine/lib/django/django/bin/profiling/gather_profile_stats.py b/google_appengine/lib/django/django/bin/profiling/gather_profile_stats.py new file mode 100755 index 0000000..852f162 --- /dev/null +++ b/google_appengine/lib/django/django/bin/profiling/gather_profile_stats.py @@ -0,0 +1,34 @@ +""" +gather_profile_stats.py /path/to/dir/of/profiles + +Note that the aggregated profiles must be read with pstats.Stats, not +hotshot.stats (the formats are incompatible) +""" + +from hotshot import stats +import pstats +import sys, os + +def gather_stats(p): + profiles = {} + for f in os.listdir(p): + if f.endswith('.agg.prof'): + path = f[:-9] + prof = pstats.Stats(os.path.join(p, f)) + elif f.endswith('.prof'): + bits = f.split('.') + path = ".".join(bits[:-3]) + prof = stats.load(os.path.join(p, f)) + else: + continue + print "Processing %s" % f + if profiles.has_key(path): + profiles[path].add(prof) + else: + profiles[path] = prof + os.unlink(os.path.join(p, f)) + for (path, prof) in profiles.items(): + prof.dump_stats(os.path.join(p, "%s.agg.prof" % path)) + +if __name__ == '__main__': + gather_stats(sys.argv[1]) diff --git a/google_appengine/lib/django/django/bin/unique-messages.py b/google_appengine/lib/django/django/bin/unique-messages.py new file mode 100755 index 0000000..c601a9e --- /dev/null +++ b/google_appengine/lib/django/django/bin/unique-messages.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import os +import sys + +def unique_messages(): + basedir = None + + if os.path.isdir(os.path.join('conf', 'locale')): + basedir = os.path.abspath(os.path.join('conf', 'locale')) + elif os.path.isdir('locale'): + basedir = os.path.abspath('locale') + else: + print "this script should be run from the django svn tree or your project or app tree" + sys.exit(1) + + for (dirpath, dirnames, filenames) in os.walk(basedir): + for f in filenames: + if f.endswith('.po'): + sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) + pf = os.path.splitext(os.path.join(dirpath, f))[0] + cmd = 'msguniq "%s.po"' % pf + stdout = os.popen(cmd) + msg = stdout.read() + open('%s.po' % pf, 'w').write(msg) + +if __name__ == "__main__": + unique_messages() diff --git a/google_appengine/lib/django/django/conf/__init__.py b/google_appengine/lib/django/django/conf/__init__.py new file mode 100755 index 0000000..021ecc8 --- /dev/null +++ b/google_appengine/lib/django/django/conf/__init__.py @@ -0,0 +1,149 @@ +""" +Settings and configuration for Django. + +Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment +variable, and then from django.conf.global_settings; see the global settings file for +a list of all possible variables. +""" + +import os +import time # Needed for Windows +from django.conf import global_settings + +ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" + +class LazySettings(object): + """ + A lazy proxy for either global Django settings or a custom settings object. + The user can manually configure settings prior to using them. Otherwise, + Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE. + """ + def __init__(self): + # _target must be either None or something that supports attribute + # access (getattr, hasattr, etc). + self._target = None + + def __getattr__(self, name): + if self._target is None: + self._import_settings() + if name == '__members__': + # Used to implement dir(obj), for example. + return self._target.get_all_members() + return getattr(self._target, name) + + def __setattr__(self, name, value): + if name == '_target': + # Assign directly to self.__dict__, because otherwise we'd call + # __setattr__(), which would be an infinite loop. + self.__dict__['_target'] = value + else: + setattr(self._target, name, value) + + def _import_settings(self): + """ + Load the settings module pointed to by the environment variable. This + is used the first time we need any settings at all, if the user has not + previously configured the settings manually. + """ + try: + settings_module = os.environ[ENVIRONMENT_VARIABLE] + if not settings_module: # If it's set but is an empty string. + raise KeyError + except KeyError: + raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE + + self._target = Settings(settings_module) + + def configure(self, default_settings=global_settings, **options): + """ + Called to manually configure the settings. The 'default_settings' + parameter sets where to retrieve any unspecified values from (its + argument must support attribute access (__getattr__)). + """ + if self._target != None: + raise EnvironmentError, 'Settings already configured.' + holder = UserSettingsHolder(default_settings) + for name, value in options.items(): + setattr(holder, name, value) + self._target = holder + +class Settings(object): + def __init__(self, settings_module): + # update this dict from global settings (but only for ALL_CAPS settings) + for setting in dir(global_settings): + if setting == setting.upper(): + setattr(self, setting, getattr(global_settings, setting)) + + # store the settings module in case someone later cares + self.SETTINGS_MODULE = settings_module + + try: + mod = __import__(self.SETTINGS_MODULE, {}, {}, ['']) + except ImportError, e: + raise EnvironmentError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) + + # Settings that should be converted into tuples if they're mistakenly entered + # as strings. + tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS") + + for setting in dir(mod): + if setting == setting.upper(): + setting_value = getattr(mod, setting) + if setting in tuple_settings and type(setting_value) == str: + setting_value = (setting_value,) # In case the user forgot the comma. + setattr(self, setting, setting_value) + + # Expand entries in INSTALLED_APPS like "django.contrib.*" to a list + # of all those apps. + new_installed_apps = [] + for app in self.INSTALLED_APPS: + if app.endswith('.*'): + appdir = os.path.dirname(__import__(app[:-2], {}, {}, ['']).__file__) + for d in os.listdir(appdir): + if d.isalpha() and os.path.isdir(os.path.join(appdir, d)): + new_installed_apps.append('%s.%s' % (app[:-2], d)) + else: + new_installed_apps.append(app) + self.INSTALLED_APPS = new_installed_apps + + if hasattr(time, 'tzset'): + # Move the time zone info into os.environ. See ticket #2315 for why + # we don't do this unconditionally (breaks Windows). + os.environ['TZ'] = self.TIME_ZONE + + def get_all_members(self): + return dir(self) + +class UserSettingsHolder(object): + """ + Holder for user configured settings. + """ + # SETTINGS_MODULE doesn't make much sense in the manually configured + # (standalone) case. + SETTINGS_MODULE = None + + def __init__(self, default_settings): + """ + Requests for configuration variables not in this class are satisfied + from the module specified in default_settings (if possible). + """ + self.default_settings = default_settings + + def __getattr__(self, name): + return getattr(self.default_settings, name) + + def get_all_members(self): + return dir(self) + dir(self.default_settings) + +settings = LazySettings() + +# This function replaces itself with django.utils.translation.gettext() the +# first time it's run. This is necessary because the import of +# django.utils.translation requires a working settings module, and loading it +# from within this file would cause a circular import. +def first_time_gettext(*args): + from django.utils.translation import gettext + __builtins__['_'] = gettext + return gettext(*args) + +__builtins__['_'] = first_time_gettext diff --git a/google_appengine/lib/django/django/conf/app_template/__init__.py b/google_appengine/lib/django/django/conf/app_template/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/google_appengine/lib/django/django/conf/app_template/models.py b/google_appengine/lib/django/django/conf/app_template/models.py new file mode 100755 index 0000000..71a8362 --- /dev/null +++ b/google_appengine/lib/django/django/conf/app_template/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/google_appengine/lib/django/django/conf/app_template/views.py b/google_appengine/lib/django/django/conf/app_template/views.py new file mode 100755 index 0000000..60f00ef --- /dev/null +++ b/google_appengine/lib/django/django/conf/app_template/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/google_appengine/lib/django/django/conf/global_settings.py b/google_appengine/lib/django/django/conf/global_settings.py new file mode 100755 index 0000000..9038b9c --- /dev/null +++ b/google_appengine/lib/django/django/conf/global_settings.py @@ -0,0 +1,330 @@ +# Default Django settings. Override these with settings in the module +# pointed-to by the DJANGO_SETTINGS_MODULE environment variable. + +# This is defined here as a do-nothing function because we can't import +# django.utils.translation -- that module depends on the settings. +gettext_noop = lambda s: s + +#################### +# CORE # +#################### + +DEBUG = False +TEMPLATE_DEBUG = False + +# Whether to use the "Etag" header. This saves bandwidth but slows down performance. +USE_ETAGS = False + +# People who get code error notifications. +# In the format (('Full Name', 'email@domain.com'), ('Full Name', 'anotheremail@domain.com')) +ADMINS = () + +# Tuple of IP addresses, as strings, that: +# * See debug comments, when DEBUG is true +# * Receive x-headers +INTERNAL_IPS = () + +# Local time zone for this installation. All choices can be found here: +# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes +# http://blogs.law.harvard.edu/tech/stories/storyReader$15 +LANGUAGE_CODE = 'en-us' + +# Languages we provide translations for, out of the box. The language name +# should be the utf-8 encoded local name for the language. +LANGUAGES = ( + ('ar', gettext_noop('Arabic')), + ('bn', gettext_noop('Bengali')), + ('ca', gettext_noop('Catalan')), + ('cs', gettext_noop('Czech')), + ('cy', gettext_noop('Welsh')), + ('da', gettext_noop('Danish')), + ('de', gettext_noop('German')), + ('el', gettext_noop('Greek')), + ('en', gettext_noop('English')), + ('es', gettext_noop('Spanish')), + ('es_AR', gettext_noop('Argentinean Spanish')), + ('fi', gettext_noop('Finnish')), + ('fr', gettext_noop('French')), + ('gl', gettext_noop('Galician')), + ('hu', gettext_noop('Hungarian')), + ('he', gettext_noop('Hebrew')), + ('is', gettext_noop('Icelandic')), + ('it', gettext_noop('Italian')), + ('ja', gettext_noop('Japanese')), + ('kn', gettext_noop('Kannada')), + ('lv', gettext_noop('Latvian')), + ('mk', gettext_noop('Macedonian')), + ('nl', gettext_noop('Dutch')), + ('no', gettext_noop('Norwegian')), + ('pl', gettext_noop('Polish')), + ('pt', gettext_noop('Portugese')), + ('pt-br', gettext_noop('Brazilian')), + ('ro', gettext_noop('Romanian')), + ('ru', gettext_noop('Russian')), + ('sk', gettext_noop('Slovak')), + ('sl', gettext_noop('Slovenian')), + ('sr', gettext_noop('Serbian')), + ('sv', gettext_noop('Swedish')), + ('ta', gettext_noop('Tamil')), + ('te', gettext_noop('Telugu')), + ('tr', gettext_noop('Turkish')), + ('uk', gettext_noop('Ukrainian')), + ('zh-cn', gettext_noop('Simplified Chinese')), + ('zh-tw', gettext_noop('Traditional Chinese')), +) + +# Languages using BiDi (right-to-left) layout +LANGUAGES_BIDI = ("he", "ar") + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Not-necessarily-technical managers of the site. They get broken link +# notifications and other various e-mails. +MANAGERS = ADMINS + +# Default content type and charset to use for all HttpResponse objects, if a +# MIME type isn't manually specified. These are used to construct the +# Content-Type header. +DEFAULT_CONTENT_TYPE = 'text/html' +DEFAULT_CHARSET = 'utf-8' + +# E-mail address that error messages come from. +SERVER_EMAIL = 'root@localhost' + +# Whether to send broken-link e-mails. +SEND_BROKEN_LINK_EMAILS = False + +# Database connection info. +DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'. +DATABASE_NAME = '' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. +DATABASE_OPTIONS = {} # Set to empty dictionary for default. + +# Host for sending e-mail. +EMAIL_HOST = 'localhost' + +# Port for sending e-mail. +EMAIL_PORT = 25 + +# Optional SMTP authentication information for EMAIL_HOST. +EMAIL_HOST_USER = '' +EMAIL_HOST_PASSWORD = '' + +# List of strings representing installed apps. +INSTALLED_APPS = () + +# List of locations of the template source files, in search order. +TEMPLATE_DIRS = () + +# List of callables that know how to import templates from various sources. +# See the comments in django/core/template/loader.py for interface +# documentation. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +# List of processors used by RequestContext to populate the context. +# Each one should be a callable that takes the request object as its +# only parameter and returns a dictionary to add to the context. +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', +# 'django.core.context_processors.request', +) + +# Output to use in template system for invalid (e.g. misspelled) variables. +TEMPLATE_STRING_IF_INVALID = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Default e-mail address to use for various automated correspondence from +# the site managers. +DEFAULT_FROM_EMAIL = 'webmaster@localhost' + +# Subject-line prefix for email messages send with django.core.mail.mail_admins +# or ...mail_managers. Make sure to include the trailing space. +EMAIL_SUBJECT_PREFIX = '[Django] ' + +# Whether to append trailing slashes to URLs. +APPEND_SLASH = True + +# Whether to prepend the "www." subdomain to URLs that don't have it. +PREPEND_WWW = False + +# List of compiled regular expression objects representing User-Agent strings +# that are not allowed to visit any page, systemwide. Use this for bad +# robots/crawlers. Here are a few examples: +# import re +# DISALLOWED_USER_AGENTS = ( +# re.compile(r'^NaverBot.*'), +# re.compile(r'^EmailSiphon.*'), +# re.compile(r'^SiteSucker.*'), +# re.compile(r'^sohu-search') +# ) +DISALLOWED_USER_AGENTS = () + +ABSOLUTE_URL_OVERRIDES = {} + +# Tuple of strings representing allowed prefixes for the {% ssi %} tag. +# Example: ('/home/html', '/var/www') +ALLOWED_INCLUDE_ROOTS = () + +# If this is a admin settings module, this should be a list of +# settings modules (in the format 'foo.bar.baz') for which this admin +# is an admin. +ADMIN_FOR = () + +# 404s that may be ignored. +IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf') +IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php') + +# A secret key for this particular Django installation. Used in secret-key +# hashing algorithms. Set this in your settings, or Django will complain +# loudly. +SECRET_KEY = '' + +# Path to the "jing" executable -- needed to validate XMLFields +JING_PATH = "/usr/bin/jing" + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. +# Example: "http://media.lawrence.com" +MEDIA_URL = '' + +# Default formatting for date objects. See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +DATE_FORMAT = 'N j, Y' + +# Default formatting for datetime objects. See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +DATETIME_FORMAT = 'N j, Y, P' + +# Default formatting for time objects. See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +TIME_FORMAT = 'P' + +# Default formatting for date objects when only the year and month are relevant. +# See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +YEAR_MONTH_FORMAT = 'F Y' + +# Default formatting for date objects when only the month and day are relevant. +# See all available format strings here: +# http://www.djangoproject.com/documentation/templates/#now +MONTH_DAY_FORMAT = 'F j' + +# Do you want to manage transactions manually? +# Hint: you really don't! +TRANSACTIONS_MANAGED = False + +# The User-Agent string to use when checking for URL validity through the +# isExistingURL validator. +URL_VALIDATOR_USER_AGENT = "Django/0.96.2 (http://www.djangoproject.com)" + +############## +# MIDDLEWARE # +############## + +# List of middleware classes to use. Order is important; in the request phase, +# this middleware classes will be applied in the order given, and in the +# response phase the middleware will be applied in reverse order. +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +# 'django.middleware.http.ConditionalGetMiddleware', +# 'django.middleware.gzip.GZipMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.doc.XViewMiddleware', +) + +############ +# SESSIONS # +############ + +SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. +SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). +SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. +SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). +SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. +SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. + +######### +# CACHE # +######### + +# The cache backend to use. See the docstring in django.core.cache for the +# possible values. +CACHE_BACKEND = 'simple://' +CACHE_MIDDLEWARE_KEY_PREFIX = '' + +#################### +# COMMENTS # +#################### + +COMMENTS_ALLOW_PROFANITIES = False + +# The profanities that will trigger a validation error in the +# 'hasNoProfanities' validator. All of these should be in lowercase. +PROFANITIES_LIST = ('asshat', 'asshead', 'asshole', 'cunt', 'fuck', 'gook', 'nigger', 'shit') + +# The group ID that designates which users are banned. +# Set to None if you're not using it. +COMMENTS_BANNED_USERS_GROUP = None + +# The group ID that designates which users can moderate comments. +# Set to None if you're not using it. +COMMENTS_MODERATORS_GROUP = None + +# The group ID that designates the users whose comments should be e-mailed to MANAGERS. +# Set to None if you're not using it. +COMMENTS_SKETCHY_USERS_GROUP = None + +# The system will e-mail MANAGERS the first COMMENTS_FIRST_FEW comments by each +# user. Set this to 0 if you want to disable it. +COMMENTS_FIRST_FEW = 0 + +# A tuple of IP addresses that have been banned from participating in various +# Django-powered features. +BANNED_IPS = () + +################## +# AUTHENTICATION # +################## + +AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) + +########### +# TESTING # +########### + +# The name of the method to use to invoke the test suite +TEST_RUNNER = 'django.test.simple.run_tests' + +# The name of the database to use for testing purposes. +# If None, a name of 'test_' + DATABASE_NAME will be assumed +TEST_DATABASE_NAME = None + +############ +# FIXTURES # +############ + +# The list of directories to search for fixtures +FIXTURE_DIRS = () diff --git a/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..323e53321daab2e7ce409922157189654170163b GIT binary patch literal 41884 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 CAvo0l literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.po new file mode 100644 index 0000000..203d50d --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/django.po @@ -0,0 +1,1989 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the django package. +# Ahmad Alhashemi , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-07-03 19:22+0300\n" +"PO-Revision-Date: 2006-07-06 23:46+0300\n" +"Last-Translator: Ahmad Alhashemi \n" +"Language-Team: Ahmad Alhashemi \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1? 0 : (n == 2? 1 : (n <= 10? 2 : 3)));\n" +"X-Poedit-Language: Arabic\n" +"X-Poedit-Country: KUWAIT\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: .\conf\global_settings.py:37 +msgid "Bengali" +msgstr "بنغالي" + +#: .\conf\global_settings.py:38 +msgid "Czech" +msgstr "تشيكي" + +#: .\conf\global_settings.py:39 +msgid "Welsh" +msgstr "ويلزي" + +#: .\conf\global_settings.py:40 +msgid "Danish" +msgstr "دنماركي" + +#: .\conf\global_settings.py:41 +msgid "German" +msgstr "ألماني" + +#: .\conf\global_settings.py:42 +msgid "Greek" +msgstr "اغريقي" + +#: .\conf\global_settings.py:43 +msgid "English" +msgstr "انجليزي" + +#: .\conf\global_settings.py:44 +msgid "Spanish" +msgstr "اسباني" + +#: .\conf\global_settings.py:45 +msgid "Argentinean Spanish" +msgstr "اسباني أرجنتيني" + +#: .\conf\global_settings.py:46 +msgid "French" +msgstr "فرنسي" + +#: .\conf\global_settings.py:47 +msgid "Galician" +msgstr "جاليسي" + +#: .\conf\global_settings.py:48 +msgid "Hungarian" +msgstr "هنغاري" + +#: .\conf\global_settings.py:49 +msgid "Hebrew" +msgstr "عبري" + +#: .\conf\global_settings.py:50 +msgid "Icelandic" +msgstr "آيسلندي" + +#: .\conf\global_settings.py:51 +msgid "Italian" +msgstr "ايطالي" + +#: .\conf\global_settings.py:52 +msgid "Japanese" +msgstr "ياباني" + +#: .\conf\global_settings.py:53 +msgid "Dutch" +msgstr "هولندي" + +#: .\conf\global_settings.py:54 +msgid "Norwegian" +msgstr "نرويجي" + +#: .\conf\global_settings.py:55 +msgid "Brazilian" +msgstr "برازيلي" + +#: .\conf\global_settings.py:56 +msgid "Romanian" +msgstr "روماني" + +#: .\conf\global_settings.py:57 +msgid "Russian" +msgstr "روسي" + +#: .\conf\global_settings.py:58 +msgid "Slovak" +msgstr "سلوفاك" + +#: .\conf\global_settings.py:59 +msgid "Slovenian" +msgstr "سلوفاتي" + +#: .\conf\global_settings.py:60 +msgid "Serbian" +msgstr "صربي" + +#: .\conf\global_settings.py:61 +msgid "Swedish" +msgstr "سويدي" + +#: .\conf\global_settings.py:62 +msgid "Ukrainian" +msgstr "أكراني" + +#: .\conf\global_settings.py:63 +msgid "Simplified Chinese" +msgstr "صيني مبسط" + +#: .\conf\global_settings.py:64 +msgid "Traditional Chinese" +msgstr "صيني تقليدي" + +#: .\contrib\admin\filterspecs.py:40 +#, python-format +msgid "" +"

By %s:

\n" +"
    \n" +msgstr "" +"

    بواسطة %s:

    \n" +"
      \n" + +#: .\contrib\admin\filterspecs.py:70 +#: .\contrib\admin\filterspecs.py:88 +#: .\contrib\admin\filterspecs.py:143 +#: .\contrib\admin\filterspecs.py:169 +msgid "All" +msgstr "كافة" + +#: .\contrib\admin\filterspecs.py:109 +msgid "Any date" +msgstr "أي تاريخ" + +#: .\contrib\admin\filterspecs.py:110 +msgid "Today" +msgstr "اليوم" + +#: .\contrib\admin\filterspecs.py:113 +msgid "Past 7 days" +msgstr "الأيام 7 الماضية" + +#: .\contrib\admin\filterspecs.py:115 +msgid "This month" +msgstr "هذا الشهر" + +#: .\contrib\admin\filterspecs.py:117 +msgid "This year" +msgstr "هذه السنة" + +#: .\contrib\admin\filterspecs.py:143 +msgid "Yes" +msgstr "نعم" + +#: .\contrib\admin\filterspecs.py:143 +msgid "No" +msgstr "لا" + +#: .\contrib\admin\filterspecs.py:150 +msgid "Unknown" +msgstr "غير معروف" + +#: .\contrib\admin\models.py:16 +msgid "action time" +msgstr "وقت العملية" + +#: .\contrib\admin\models.py:19 +msgid "object id" +msgstr "معرف العنصر" + +#: .\contrib\admin\models.py:20 +msgid "object repr" +msgstr "ممثل العنصر" + +#: .\contrib\admin\models.py:21 +msgid "action flag" +msgstr "علامة العملية" + +#: .\contrib\admin\models.py:22 +msgid "change message" +msgstr "تغيير الرسالة" + +#: .\contrib\admin\models.py:25 +msgid "log entry" +msgstr "مدخل السجل" + +#: .\contrib\admin\models.py:26 +msgid "log entries" +msgstr "مدخلات السجل" + +#: .\contrib\admin\templates\admin\404.html.py:4 +#: .\contrib\admin\templates\admin\404.html.py:8 +msgid "Page not found" +msgstr "تعذر العثور على الصفحة" + +#: .\contrib\admin\templates\admin\404.html.py:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "نحن آسفون، لكننا لم نعثر على الصفحة المطلوبة." + +#: .\contrib\admin\templates\admin\500.html.py:4 +#: .\contrib\admin\templates\admin\base.html.py:29 +#: .\contrib\admin\templates\admin\change_form.html.py:13 +#: .\contrib\admin\templates\admin\change_list.html.py:6 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:6 +#: .\contrib\admin\templates\admin\invalid_setup.html.py:4 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\logged_out.html.py:4 +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +msgid "Home" +msgstr "الرئيسية" + +#: .\contrib\admin\templates\admin\500.html.py:4 +msgid "Server error" +msgstr "خطأ في المزود" + +#: .\contrib\admin\templates\admin\500.html.py:6 +msgid "Server error (500)" +msgstr "خطأ في المزود (500)" + +#: .\contrib\admin\templates\admin\500.html.py:9 +msgid "Server Error (500)" +msgstr "خطأ في المزود (500)" + +#: .\contrib\admin\templates\admin\500.html.py:10 +msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." +msgstr "حدث خطأ، وقم تم الابلاغ عنه إلى مدراء الموقع عبر البريد الإلكترونيوسيتم حل المشكلة قريبا إن شاء الله، شكرا لك على صبرك." + +#: .\contrib\admin\templates\admin\base.html.py:24 +msgid "Welcome," +msgstr "أهلا، " + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Documentation" +msgstr "التعليمات" + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Change password" +msgstr "تغيير كلمة المرور" + +#: .\contrib\admin\templates\admin\base.html.py:24 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Log out" +msgstr "خروج" + +#: .\contrib\admin\templates\admin\base_site.html.py:4 +msgid "Django site admin" +msgstr "إدارة موقع جاننغو" + +#: .\contrib\admin\templates\admin\base_site.html.py:7 +msgid "Django administration" +msgstr "إدارة جانغو" + +#: .\contrib\admin\templates\admin\change_form.html.py:15 +#: .\contrib\admin\templates\admin\index.html.py:28 +msgid "Add" +msgstr "إضافة" + +#: .\contrib\admin\templates\admin\change_form.html.py:20 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +msgid "History" +msgstr "تاريخ" + +#: .\contrib\admin\templates\admin\change_form.html.py:21 +msgid "View on site" +msgstr "عرض على الموقع" + +#: .\contrib\admin\templates\admin\change_form.html.py:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "الرجاء اصلاح الخطأ التالي." +msgstr[1] "الرجاء اصلاح الخطئين التاليين." +msgstr[2] "الرجاء اصلاح الأخطاء التالية." +msgstr[3] "الرجاء اصلاح الأخطاء التالية." + +#: .\contrib\admin\templates\admin\change_form.html.py:48 +msgid "Ordering" +msgstr "الترتيب" + +#: .\contrib\admin\templates\admin\change_form.html.py:51 +msgid "Order:" +msgstr "الترتيب" + +#: .\contrib\admin\templates\admin\change_list.html.py:11 +#, python-format +msgid "Add %(name)s" +msgstr "اضافة %(name)s" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:9 +#: .\contrib\admin\templates\admin\submit_line.html.py:3 +msgid "Delete" +msgstr "حذف" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:14 +#, python-format +msgid "Deleting the %(object_name)s '%(object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "حذف %(object_name)s '%(object)s' سيؤدي إلى حذف العناصر المتعلقة به، لكن هذا الحساب لا يملك الصلاحية لحذف أنواع العناصر التالية:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:21 +#, python-format +msgid "Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of the following related items will be deleted:" +msgstr "هل أنت متأكد من أنك تريد حذف %(object_name)s \"%(object)s\"? كافة العناصر التالية المتعلقة به سيتم حذفها:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:26 +msgid "Yes, I'm sure" +msgstr "نعم، أنا متأكد" + +#: .\contrib\admin\templates\admin\filter.html.py:2 +#, python-format +msgid " By %(title)s " +msgstr " بواسطة %(title)s " + +#: .\contrib\admin\templates\admin\filters.html.py:4 +msgid "Filter" +msgstr "مرشح" + +#: .\contrib\admin\templates\admin\index.html.py:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "النماذج المتوفرة في برنامج %(name)s." + +#: .\contrib\admin\templates\admin\index.html.py:34 +msgid "Change" +msgstr "تغيير" + +#: .\contrib\admin\templates\admin\index.html.py:44 +msgid "You don't have permission to edit anything." +msgstr "ليست لديك الصلاحية لتعديل أي شيء." + +#: .\contrib\admin\templates\admin\index.html.py:52 +msgid "Recent Actions" +msgstr "العمليات الأخيرة" + +#: .\contrib\admin\templates\admin\index.html.py:53 +msgid "My Actions" +msgstr "عملياتي" + +#: .\contrib\admin\templates\admin\index.html.py:57 +msgid "None available" +msgstr "لا يوجد" + +#: .\contrib\admin\templates\admin\invalid_setup.html.py:8 +msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." +msgstr "هنالك أمر خاطئ في تركيب قاعدة بياناتك، تأكد من أنه تم انشاء جداول قاعدة البيانات الملائمة، وتأكد من أن قاعدة البيانات قابلة للقراءة من قبل المستخدم الملائم." + +#: .\contrib\admin\templates\admin\login.html.py:17 +#: .\contrib\comments\templates\comments\form.html.py:6 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Username:" +msgstr "اسم المستخدم:" + +#: .\contrib\admin\templates\admin\login.html.py:20 +#: .\contrib\comments\templates\comments\form.html.py:6 +msgid "Password:" +msgstr "كلمة المرور:" + +#: .\contrib\admin\templates\admin\login.html.py:22 +msgid "Have you forgotten your password?" +msgstr "Have you forgotten your password?" + +#: .\contrib\admin\templates\admin\login.html.py:25 +#: .\contrib\admin\views\decorators.py:24 +msgid "Log in" +msgstr "دخول" + +#: .\contrib\admin\templates\admin\object_history.html.py:18 +msgid "Date/time" +msgstr "التاريخ/الوقت" + +#: .\contrib\admin\templates\admin\object_history.html.py:19 +msgid "User" +msgstr "المستخدم" + +#: .\contrib\admin\templates\admin\object_history.html.py:20 +msgid "Action" +msgstr "العملية" + +#: .\contrib\admin\templates\admin\object_history.html.py:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "" + +#: .\contrib\admin\templates\admin\object_history.html.py:36 +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "هذا العنصر لا يملك تاريخ تغييرات، على الأغلب أن هذا العنصر لم يتم انشاءه عبر نظام الإدارة هذا." + +#: .\contrib\admin\templates\admin\pagination.html.py:10 +msgid "Show all" +msgstr "عرض الكل" + +#: .\contrib\admin\templates\admin\search_form.html.py:8 +msgid "Go" +msgstr "انطلق" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "نتيحة واحدة" +msgstr[1] "نتيجتان" +msgstr[2] "%(counter)s نتائج" +msgstr[3] "%(counter)s نتيحة" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "المجموع %(full_result_count)s" + +#: .\contrib\admin\templates\admin\submit_line.html.py:4 +msgid "Save as new" +msgstr "حفظ كجديد" + +#: .\contrib\admin\templates\admin\submit_line.html.py:5 +msgid "Save and add another" +msgstr "حفظ وإضافة آخر" + +#: .\contrib\admin\templates\admin\submit_line.html.py:6 +msgid "Save and continue editing" +msgstr "حفظ واستمرار التعديل" + +#: .\contrib\admin\templates\admin\submit_line.html.py:7 +msgid "Save" +msgstr "حفظ" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +msgid "Bookmarklets" +msgstr "أوامر المفضلة" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:5 +msgid "Documentation bookmarklets" +msgstr "أوامر مفضلة التعليمات" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:9 +msgid "" +"\n" +"

      To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

      \n" +msgstr "" +"\n" +"

      لتركيب أوامر المفضلة، قم بسحب الوصلة إلى\n" +"شريط أدات المفضلات في متصفحك، أو قم بالضغط عليها بالزر الأيمن وأضفها إلى مفضلاتك.\n" +"سيمكنك الآن أن اختيار أوامر المفضلة من أي صفحة في الموقع، لاحظ بأن بعض\n" +"أوامر المفضلة هذه معدة لتعمل على أجهزة كمبيوتر تعتبر على أنها \"داخلية\"\n" +"(تحدث إلى مسؤول النظم إذا لم تكن متأكدا ما إذا كان كمبيوتر يعتبر \"داخليا\").

      \n" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:19 +msgid "Documentation for this page" +msgstr "التعليمات لهذه الصفحة" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:20 +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "ينتقل بك من أي صفحة إلى تعليمات العرض الذي أنشأ هذه الصفحة." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:22 +msgid "Show object ID" +msgstr "عرض معرف الكائن" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:23 +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "عرض نوع البيانات والمعرف الفريد للصفحات التي تمثل كائنا واحدا." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:25 +msgid "Edit this object (current window)" +msgstr "تعديل هذا الكائن (النافذة الحالية)" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "ينتقل بك إلى صفحة الإدارة للصفحات التي تمثل كائنا واحدا." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:28 +msgid "Edit this object (new window)" +msgstr "تعديل هذا العنصر (نافذة جديدة)" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:29 +msgid "As above, but opens the admin page in a new window." +msgstr "كما سبق، لكن يفتح صفحة الإدارة في نافذة جديدة." + +#: .\contrib\admin\templates\registration\logged_out.html.py:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "شكرا لك على قضائك بعض الوقت مع الموقع اليوم." + +#: .\contrib\admin\templates\registration\logged_out.html.py:10 +msgid "Log in again" +msgstr "دخول مرة أخرى" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:6 +#: .\contrib\admin\templates\registration\password_change_form.html.py:10 +msgid "Password change" +msgstr "تغيير كلمة المرور" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:6 +#: .\contrib\admin\templates\registration\password_change_done.html.py:10 +msgid "Password change successful" +msgstr "تم تغيير كلمة المرور بنجاح" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:12 +msgid "Your password was changed." +msgstr "لقد تغيرت كلمة مرورك." + +#: .\contrib\admin\templates\registration\password_change_form.html.py:12 +msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." +msgstr "الرجاء ادخال كلمة مرورك القديمة، زيادة في الأمان، ثم ادخل كلمة مرورك الجديدة مرتين لنكون متأكدين من أنك كتبتها بصورة صحيحة." + +#: .\contrib\admin\templates\registration\password_change_form.html.py:17 +msgid "Old password:" +msgstr "كلمة المرور القديمة:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:19 +msgid "New password:" +msgstr "كلمة المرور الجديدة:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:21 +msgid "Confirm password:" +msgstr "تأكيد كلمة المرور:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:23 +msgid "Change my password" +msgstr "تغيير كلمة المرور الخاصة بي" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:10 +msgid "Password reset" +msgstr "اعادة ضبط كلمة المرور" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:10 +msgid "Password reset successful" +msgstr "تمت عملية اعادة ضبط كلمة المرور بنجاح" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:12 +msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." +msgstr "لقد قمنا بارسال كلمة مرور جديدة إلى عنوان البريد الإلكتروني الذي أدخلتها، ستصلك قريبا إن شاء الله." + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "لقد وصلتك رسالة البريد الإلكتروني هذه لأنك طلبت إعادة ضبط كلمة المرور." + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "لحسابك المستخدم الخاص بك في %(site_name)s" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "كلمة مرورك الجديدة هي: %(new_password)s" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:7 +msgid "Feel free to change this password by going to this page:" +msgstr "يمكنك تغيير كلمة المرور هذه بالذهاب إلى هذه الصفحة:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:11 +msgid "Your username, in case you've forgotten:" +msgstr "اسم المستخدم الخاص بك، في حال كنت قد نسيته:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:13 +msgid "Thanks for using our site!" +msgstr "شكرا لاستخدامك لموقعنا!" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "فريق %(site_name)s" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:12 +msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." +msgstr "نسيت كلمة المرور الخاصة بك؟ قم بادخال عنوان بريدك الإلكتروني بالأسفل وسنقوم باعادة ضبط كلمة المرور الخاصة بك وارسال كلمة المرور الجديدة إليك عبر البريد الإلكتروني." + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:16 +msgid "E-mail address:" +msgstr "عنوان البريد الإلكتروني:" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:16 +msgid "Reset my password" +msgstr "اعادة ضبط كلمة المرور" + +#: .\contrib\admin\templates\widget\date_time.html.py:3 +msgid "Date:" +msgstr "التاريخ:" + +#: .\contrib\admin\templates\widget\date_time.html.py:4 +msgid "Time:" +msgstr "الوقت:" + +#: .\contrib\admin\templates\widget\file.html.py:2 +msgid "Currently:" +msgstr "حاليا:" + +#: .\contrib\admin\templates\widget\file.html.py:3 +msgid "Change:" +msgstr "تغيير:" + +#: .\contrib\admin\templatetags\admin_list.py:230 +msgid "All dates" +msgstr "كافة التواريخ" + +#: .\contrib\admin\views\decorators.py:10 +#: .\contrib\auth\forms.py:37 +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +msgstr "الرجاء ادخال اسم مستخدم وكلمة مرور صحيحين، الرجاء الانتباه إلى أن كلا الحقلين حساس لحالة الأحرف من حيث كونها كبيرة أو صغيرة." + +#: .\contrib\admin\views\decorators.py:62 +msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." +msgstr "الرجاء الدخول مرة أخرى لأن جلستك انتهت، لا تقلق: البيانات التي قمت بارسالها حفظت." + +#: .\contrib\admin\views\decorators.py:69 +msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." +msgstr "يبدو بأن متصفحك غير معد لقبول الكوكيز، قم بتفعيل الكوكيز من فضلك ثم تحديث هذه الصفحة والمحاولة مرة أخرى." + +#: .\contrib\admin\views\decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "اسم المستخدم يجب أن لا يحتوي على علامة '@'." + +#: .\contrib\admin\views\decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "بريدك الإلكتروني ليس اسم المستخدم الخاص بك، جرب استخدام '%s' بدلا من ذلك." + +#: .\contrib\admin\views\doc.py:291 +#: .\contrib\admin\views\doc.py:301 +#: .\contrib\admin\views\doc.py:303 +#: .\contrib\admin\views\doc.py:309 +#: .\contrib\admin\views\doc.py:310 +#: .\contrib\admin\views\doc.py:312 +msgid "Integer" +msgstr "عدد صحيح" + +#: .\contrib\admin\views\doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "ثنائي (إما صح أو خطأ)" + +#: .\contrib\admin\views\doc.py:293 +#: .\contrib\admin\views\doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "سلسلة نصية (تصل إلى %(maxlength)s)" + +#: .\contrib\admin\views\doc.py:294 +msgid "Comma-separated integers" +msgstr "أرقام صحيحة مفصولة بفواصل comma" + +#: .\contrib\admin\views\doc.py:295 +msgid "Date (without time)" +msgstr "التاريخ (بدون الوقت)" + +#: .\contrib\admin\views\doc.py:296 +msgid "Date (with time)" +msgstr "التاريخ (مع الوقت)" + +#: .\contrib\admin\views\doc.py:297 +msgid "E-mail address" +msgstr "عنوان البريد الإلكتروني" + +#: .\contrib\admin\views\doc.py:298 +#: .\contrib\admin\views\doc.py:299 +#: .\contrib\admin\views\doc.py:302 +msgid "File path" +msgstr "مسار الملف" + +#: .\contrib\admin\views\doc.py:300 +msgid "Decimal number" +msgstr "رقم عشري" + +#: .\contrib\admin\views\doc.py:304 +#: .\contrib\comments\models.py:85 +msgid "IP address" +msgstr "عنوان IP" + +#: .\contrib\admin\views\doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "ثنائي (إما صح أو خطأ أو لاشيء)" + +#: .\contrib\admin\views\doc.py:307 +msgid "Relation to parent model" +msgstr "العلاقة بالنموذج الأب" + +#: .\contrib\admin\views\doc.py:308 +msgid "Phone number" +msgstr "رقم هاتف" + +#: .\contrib\admin\views\doc.py:313 +msgid "Text" +msgstr "نص" + +#: .\contrib\admin\views\doc.py:314 +msgid "Time" +msgstr "وقت" + +#: .\contrib\admin\views\doc.py:315 +#: .\contrib\flatpages\models.py:7 +msgid "URL" +msgstr "وصلة" + +#: .\contrib\admin\views\doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "ولاية أمريكية (حرفان كبيران)" + +#: .\contrib\admin\views\doc.py:317 +msgid "XML text" +msgstr "نص XML" + +#: .\contrib\admin\views\main.py:226 +msgid "Site administration" +msgstr "إدارة الموقع" + +#: .\contrib\admin\views\main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "تم اضافة %(name)s \"%(obj)s\" بنجاح." + +#: .\contrib\admin\views\main.py:264 +#: .\contrib\admin\views\main.py:348 +msgid "You may edit it again below." +msgstr "يمكنك تعديله مجددا في الأسفل." + +#: .\contrib\admin\views\main.py:272 +#: .\contrib\admin\views\main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "يمكنك إضافة %s آخر بالأسفل." + +#: .\contrib\admin\views\main.py:290 +#, python-format +msgid "Add %s" +msgstr "اضافة %s" + +#: .\contrib\admin\views\main.py:336 +#, python-format +msgid "Added %s." +msgstr "اضاف %s." + +#: .\contrib\admin\views\main.py:336 +#: .\contrib\admin\views\main.py:338 +#: .\contrib\admin\views\main.py:340 +msgid "and" +msgstr "و" + +#: .\contrib\admin\views\main.py:338 +#, python-format +msgid "Changed %s." +msgstr "غير %s." + +#: .\contrib\admin\views\main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "حذف %s." + +#: .\contrib\admin\views\main.py:343 +msgid "No fields changed." +msgstr "لم يتم تغيير أية حقول." + +#: .\contrib\admin\views\main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "تم تغيير %(name)s \"%(obj)s\" بنجاح." + +#: .\contrib\admin\views\main.py:354 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "تم اضافة %(name)s \"%(obj)s\" بنجاح، يمكنك تعديله مرة أخرى بالأسفل." + +#: .\contrib\admin\views\main.py:392 +#, python-format +msgid "Change %s" +msgstr "تغيير %s" + +#: .\contrib\admin\views\main.py:474 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "%(fieldname)s واحد أو أكثر في %(name)s: %(obj)s" + +#: .\contrib\admin\views\main.py:479 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "%(fieldname)s واحد أو أكثر في %(name)s:" + +#: .\contrib\admin\views\main.py:512 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "تم حذف %(name)s \"%(obj)s\" بنجاح." + +#: .\contrib\admin\views\main.py:515 +msgid "Are you sure?" +msgstr "هل أنت متأكد؟" + +#: .\contrib\admin\views\main.py:537 +#, python-format +msgid "Change history: %s" +msgstr "تاريخ التغيير: %s" + +#: .\contrib\admin\views\main.py:571 +#, python-format +msgid "Select %s" +msgstr "اختر %s" + +#: .\contrib\admin\views\main.py:571 +#, python-format +msgid "Select %s to change" +msgstr "اختر %s لتغييره" + +#: .\contrib\admin\views\main.py:747 +msgid "Database error" +msgstr "خطـأ في قاعدة البيانات" + +#: .\contrib\auth\forms.py:30 +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "يبدو بأن الكوكيز غير مفعله في متصفحك، الكوكيز مطلوبة للتمكن من الدخول." + +#: .\contrib\auth\forms.py:39 +msgid "This account is inactive." +msgstr "هذا الحساب غير فعال." + +#: .\contrib\auth\models.py:37 +#: .\contrib\auth\models.py:56 +msgid "name" +msgstr "الاسم" + +#: .\contrib\auth\models.py:39 +msgid "codename" +msgstr "الاسم الرمزي" + +#: .\contrib\auth\models.py:41 +msgid "permission" +msgstr "الصلاحية" + +#: .\contrib\auth\models.py:42 +#: .\contrib\auth\models.py:57 +msgid "permissions" +msgstr "الصلاحيات" + +#: .\contrib\auth\models.py:59 +msgid "group" +msgstr "المجموعة" + +#: .\contrib\auth\models.py:60 +#: .\contrib\auth\models.py:99 +msgid "groups" +msgstr "المجموعات" + +#: .\contrib\auth\models.py:89 +msgid "username" +msgstr "اسم المستخدم" + +#: .\contrib\auth\models.py:89 +msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." +msgstr "مطلوب. 30 خانة أو أقل. خانات حرف رقمية فقط (أحرف، أرقام والشرطة السفلية)." + +#: .\contrib\auth\models.py:90 +msgid "first name" +msgstr "الاسم الأول" + +#: .\contrib\auth\models.py:91 +msgid "last name" +msgstr "الاسم الأخير" + +#: .\contrib\auth\models.py:92 +msgid "e-mail address" +msgstr "عنوان البريد الإلكتروني" + +#: .\contrib\auth\models.py:93 +msgid "password" +msgstr "كلمة المرور" + +#: .\contrib\auth\models.py:93 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "استخدم '[algo]$[salt]$[hexdigest]'" + +#: .\contrib\auth\models.py:94 +msgid "staff status" +msgstr "حالة الطاقم" + +#: .\contrib\auth\models.py:94 +msgid "Designates whether the user can log into this admin site." +msgstr "يحدد ما إذا كان المستخدم يستطيع الدخول إلى موقع الإدارة هذا." + +#: .\contrib\auth\models.py:95 +msgid "active" +msgstr "فعال" + +#: .\contrib\auth\models.py:95 +msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." +msgstr "يحدد ما إذا كان المستخدم يستطيع الدخول إلى لوحة تحكم جانغو، قم بتحديد هذا الخيار بدلا من حذف حسابات المستخدمين." + +#: .\contrib\auth\models.py:96 +msgid "superuser status" +msgstr "حالة المستخدم بالقوى الخارقة" + +#: .\contrib\auth\models.py:96 +msgid "Designates that this user has all permissions without explicitly assigning them." +msgstr "حدد بأن هذا المستخدم يمتلك كافة الصلاحيات دون الحاجة لتحديدها له تصريحا." + +#: .\contrib\auth\models.py:97 +msgid "last login" +msgstr "آخر عملية دخول" + +#: .\contrib\auth\models.py:98 +msgid "date joined" +msgstr "تاريخ الانضمام" + +#: .\contrib\auth\models.py:100 +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "بالإضافة إلى الصلاحيات المحددة للمستخدم يدويا، فإن المستخدم يحصل أيضا على كافة صلاحيات المجموعة التي ينتمي إليها." + +#: .\contrib\auth\models.py:101 +msgid "user permissions" +msgstr "صلاحيات المستخدم" + +#: .\contrib\auth\models.py:104 +msgid "user" +msgstr "المستخدم" + +#: .\contrib\auth\models.py:105 +msgid "users" +msgstr "المستخدمين" + +#: .\contrib\auth\models.py:110 +msgid "Personal info" +msgstr "المعلومات الشخصية" + +#: .\contrib\auth\models.py:111 +msgid "Permissions" +msgstr "الصلاحيات" + +#: .\contrib\auth\models.py:112 +msgid "Important dates" +msgstr "تواريخ مهمة" + +#: .\contrib\auth\models.py:113 +msgid "Groups" +msgstr "المجموعات" + +#: .\contrib\auth\models.py:250 +msgid "message" +msgstr "رسالة" + +#: .\contrib\auth\views.py:40 +msgid "Logged out" +msgstr "خروج" + +#: .\contrib\comments\models.py:67 +#: .\contrib\comments\models.py:166 +msgid "object ID" +msgstr "معرف العنصر" + +#: .\contrib\comments\models.py:68 +msgid "headline" +msgstr "عنوان" + +#: .\contrib\comments\models.py:69 +#: .\contrib\comments\models.py:90 +#: .\contrib\comments\models.py:167 +msgid "comment" +msgstr "تعليق" + +#: .\contrib\comments\models.py:70 +msgid "rating #1" +msgstr "تقييم #1" + +#: .\contrib\comments\models.py:71 +msgid "rating #2" +msgstr "تقييم #2" + +#: .\contrib\comments\models.py:72 +msgid "rating #3" +msgstr "تقييم #3" + +#: .\contrib\comments\models.py:73 +msgid "rating #4" +msgstr "تقييم #4" + +#: .\contrib\comments\models.py:74 +msgid "rating #5" +msgstr "تقييم #5" + +#: .\contrib\comments\models.py:75 +msgid "rating #6" +msgstr "تقييم #8" + +#: .\contrib\comments\models.py:76 +msgid "rating #7" +msgstr "تقييم #7" + +#: .\contrib\comments\models.py:77 +msgid "rating #8" +msgstr "تقييم #8" + +#: .\contrib\comments\models.py:82 +msgid "is valid rating" +msgstr "تقييم صالح" + +#: .\contrib\comments\models.py:83 +#: .\contrib\comments\models.py:169 +msgid "date/time submitted" +msgstr "تم ارسال التاريخ/الوقت" + +#: .\contrib\comments\models.py:84 +#: .\contrib\comments\models.py:170 +msgid "is public" +msgstr "عام" + +#: .\contrib\comments\models.py:86 +msgid "is removed" +msgstr "محذوف" + +#: .\contrib\comments\models.py:86 +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "قم بتحديد هذا المربع إذا كان التعليق غير لائق، سيتم عرض الرسالة \"تم حذف هذا التعليق\" بدلا منه." + +#: .\contrib\comments\models.py:91 +msgid "comments" +msgstr "تعليقات" + +#: .\contrib\comments\models.py:131 +#: .\contrib\comments\models.py:207 +msgid "Content object" +msgstr "عنصر محتوى" + +#: .\contrib\comments\models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"أرسلت بواسطة %(user)s في %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: .\contrib\comments\models.py:168 +msgid "person's name" +msgstr "اسم الشخص" + +#: .\contrib\comments\models.py:171 +msgid "ip address" +msgstr "عنوان ip" + +#: .\contrib\comments\models.py:173 +msgid "approved by staff" +msgstr "موافق عليه من قبل الطاقم" + +#: .\contrib\comments\models.py:176 +msgid "free comment" +msgstr "تعليق حر" + +#: .\contrib\comments\models.py:177 +msgid "free comments" +msgstr "تعليقات حرة" + +#: .\contrib\comments\models.py:233 +msgid "score" +msgstr "الدرجة" + +#: .\contrib\comments\models.py:234 +msgid "score date" +msgstr "تاريخ الدرجة" + +#: .\contrib\comments\models.py:237 +msgid "karma score" +msgstr "درجة الكارما" + +#: .\contrib\comments\models.py:238 +msgid "karma scores" +msgstr "درجات الكارما" + +#: .\contrib\comments\models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "تقييم %(score)d بواسطة %(user)s" + +#: .\contrib\comments\models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"هذا التعليق تم تعليمه بواسطة %(user)s:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\models.py:265 +msgid "flag date" +msgstr "تاريخ التعليم" + +#: .\contrib\comments\models.py:268 +msgid "user flag" +msgstr "علامة مستخدم" + +#: .\contrib\comments\models.py:269 +msgid "user flags" +msgstr "علامات المستخدم" + +#: .\contrib\comments\models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "علامة بواسطة %r" + +#: .\contrib\comments\models.py:278 +msgid "deletion date" +msgstr "تاريخ الحذف" + +#: .\contrib\comments\models.py:280 +msgid "moderator deletion" +msgstr "حذف المراقب" + +#: .\contrib\comments\models.py:281 +msgid "moderator deletions" +msgstr "حذوفات المراقب" + +#: .\contrib\comments\models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "حذف المراقب بواسطة %r" + +#: .\contrib\comments\templates\comments\form.html.py:6 +msgid "Forgotten your password?" +msgstr "نسيت كلمة المرور؟" + +#: .\contrib\comments\templates\comments\form.html.py:12 +msgid "Ratings" +msgstr "التقييمات" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Required" +msgstr "مطلوب" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Optional" +msgstr "اختياري" + +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Post a photo" +msgstr "ارسال صورة" + +#: .\contrib\comments\templates\comments\form.html.py:28 +#: .\contrib\comments\templates\comments\freeform.html.py:5 +msgid "Comment:" +msgstr "تعليق:" + +#: .\contrib\comments\templates\comments\form.html.py:34 +#: .\contrib\comments\templates\comments\freeform.html.py:9 +msgid "Preview comment" +msgstr "استعراض التعليق" + +#: .\contrib\comments\templates\comments\freeform.html.py:4 +msgid "Your name:" +msgstr "اسمك:" + +#: .\contrib\comments\views\comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "هذا التقييم مطلوب لأنك قمت بادخال تقييم واحد على الأقل." + +#: .\contrib\comments\views\comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من تعليق واحد:\n" +"\n" +"%(text)s" +msgstr[1] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من تعليقان:\n" +"\n" +"%(text)s" +msgstr[2] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من %(count)s تعليقات:\n" +"\n" +"%(text)s" +msgstr[3] "" +"هذا التعليق كتب بواسطة شخص لديه أقل من %(count)s تعليق:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"هذا التعليق كتب بواسطة عضو سطحي:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:188 +#: .\contrib\comments\views\comments.py:280 +msgid "Only POSTs are allowed" +msgstr "يسمح باستخدام POST فقط" + +#: .\contrib\comments\views\comments.py:192 +#: .\contrib\comments\views\comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "لم يتم ارسال واحد أو أكثر من الحقول المطلوبة." + +#: .\contrib\comments\views\comments.py:196 +#: .\contrib\comments\views\comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "شخص ما قام بالتلاعب بنموذج التعليق (انتهاك أمني)" + +#: .\contrib\comments\views\comments.py:206 +#: .\contrib\comments\views\comments.py:292 +msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgstr "نموذج التعليق احتوى 'هدف' غير صحيح -- معرف الكائن كان غير صحيحا" + +#: .\contrib\comments\views\comments.py:257 +#: .\contrib\comments\views\comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "نموذج التعليق لم يحدد أيا من 'استعراض' أو 'ارسال'" + +#: .\contrib\comments\views\karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "لا يمكن للمستخدمين المجهولين التصويت" + +#: .\contrib\comments\views\karma.py:23 +msgid "Invalid comment ID" +msgstr "معرف التعليق غير صحيح" + +#: .\contrib\comments\views\karma.py:25 +msgid "No voting for yourself" +msgstr "لا يمكنك التصويت لنفسك" + +#: .\contrib\contenttypes\models.py:20 +msgid "python model class name" +msgstr "اسم صنف النموذج في python" + +#: .\contrib\contenttypes\models.py:23 +msgid "content type" +msgstr "نوع البيانات" + +#: .\contrib\contenttypes\models.py:24 +msgid "content types" +msgstr "أنواع البيانات" + +#: .\contrib\flatpages\models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "مثال: '/about/contact/'. تأكد من وضع شرطات في البداية والنهاية." + +#: .\contrib\flatpages\models.py:9 +msgid "title" +msgstr "العنوان" + +#: .\contrib\flatpages\models.py:10 +msgid "content" +msgstr "المحتوى" + +#: .\contrib\flatpages\models.py:11 +msgid "enable comments" +msgstr "السماح بالتعليقات" + +#: .\contrib\flatpages\models.py:12 +msgid "template name" +msgstr "اسم القالب" + +#: .\contrib\flatpages\models.py:13 +msgid "Example: 'flatpages/contact_page'. If this isn't provided, the system will use 'flatpages/default'." +msgstr "مثال: 'flatpages/contact_page'. إذا لم يتم تحديده فإن النظام سيقوم باستخدام 'flatpages/default'." + +#: .\contrib\flatpages\models.py:14 +msgid "registration required" +msgstr "التسجيل مطلوب" + +#: .\contrib\flatpages\models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "إذا كان هذا الخيار محددا، فإن المستخدمين الداخلين فقط سيتمكنون من مشاهدة الصفحة." + +#: .\contrib\flatpages\models.py:18 +msgid "flat page" +msgstr "صفحة مسطحة" + +#: .\contrib\flatpages\models.py:19 +msgid "flat pages" +msgstr "صفحات مسطحة" + +#: .\contrib\redirects\models.py:7 +msgid "redirect from" +msgstr "نموذج إعادة توجيه" + +#: .\contrib\redirects\models.py:8 +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "يجب أن يكون هذا مسارا مطلقا وبدون اسم النطاق. مثال: '/events/search/'." + +#: .\contrib\redirects\models.py:9 +msgid "redirect to" +msgstr "إعادة توجيه إلى" + +#: .\contrib\redirects\models.py:10 +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "يجب أن يكون هذا مسارا مطلقا (كما في الذي فوقه) عنوانا كاملا يبدأ بالمقطع 'http://'." + +#: .\contrib\redirects\models.py:13 +msgid "redirect" +msgstr "إعادة توجيه" + +#: .\contrib\redirects\models.py:14 +msgid "redirects" +msgstr "إعادات توجيه" + +#: .\contrib\sessions\models.py:41 +msgid "session key" +msgstr "مفتاح الجلسة" + +#: .\contrib\sessions\models.py:42 +msgid "session data" +msgstr "بيانات الجلسة" + +#: .\contrib\sessions\models.py:43 +msgid "expire date" +msgstr "تاريخ الانتهاء" + +#: .\contrib\sessions\models.py:47 +msgid "session" +msgstr "جلسة" + +#: .\contrib\sessions\models.py:48 +msgid "sessions" +msgstr "جلسات" + +#: .\contrib\sites\models.py:10 +msgid "domain name" +msgstr "اسم النطاق" + +#: .\contrib\sites\models.py:11 +msgid "display name" +msgstr "اسم العرض" + +#: .\contrib\sites\models.py:15 +msgid "site" +msgstr "موقع" + +#: .\contrib\sites\models.py:16 +msgid "sites" +msgstr "مواقع" + +#: .\core\validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "هذه القيمة يجب أن تحتوي فقط على الأحرف والأرقام والشرطة السفلية." + +#: .\core\validators.py:67 +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "هذه القيمة يجب أن تحتوي فقط على الأحرف والأرقام والشرطات السفلية والشرطة العادية والشرطات المائلة." + +#: .\core\validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "الحروف الكبيرة غير مسموح بها هنا." + +#: .\core\validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "الحروف الصغيرة غير مسموح بها هنا." + +#: .\core\validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "أدخل أرقاما فقط مفصول بينها بفواصل comma." + +#: .\core\validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "أدخل عناوين بريد إلكتروني صالحة مفصول بينها بفواصل comma." + +#: .\core\validators.py:102 +msgid "Please enter a valid IP address." +msgstr "أدخل عنوان IP صالح من فضلك." + +#: .\core\validators.py:106 +msgid "Empty values are not allowed here." +msgstr "القيم الفارغة غير مسموح بها هنا." + +#: .\core\validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "الخانات غير الرقمية غير مسموح بها هنا." + +#: .\core\validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "لا يمكن أن تكون القيمة مكونة من الأرقام فقط." + +#: .\core\validators.py:119 +msgid "Enter a whole number." +msgstr "أدخل رقما صحيحا." + +#: .\core\validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "فقط الخانات الحرفية مسموح بها هنا." + +#: .\core\validators.py:127 +#: .\db\models\fields\__init__.py:412 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "أدخل تاريخا صحيحا بتنسيق YYYY-MM-DD." + +#: .\core\validators.py:131 +msgid "Enter a valid time in HH:MM format." +msgstr "أدخل وقتا صحيحا بتنسيق HH:MM." + +#: .\core\validators.py:135 +#: .\db\models\fields\__init__.py:474 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "أدخل تاريخ/وقت صحيحين بتنسيق YYYY-MM-DD HH:MM." + +#: .\core\validators.py:139 +msgid "Enter a valid e-mail address." +msgstr "أدخل عنوان بريد إلكتروني صحيح." + +#: .\core\validators.py:151 +#: .\core\validators.py:379 +#: .\forms\__init__.py:659 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "لم يتم ارسال ملف، الرجاء التأكد من نوع ترميز (encoding type) النموذج." + +#: .\core\validators.py:155 +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "قم برفع صورة صالحة، الملف الذي قمت برفعه إما أنه ليس ملفا لصورة أو أنه ملف معطوب." + +#: .\core\validators.py:162 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "العنوان %s لا يحتوي على صورة صالحة." + +#: .\core\validators.py:166 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "رقم الهاتف يجب أن يكون بتنسيق XXX-XXX-XXXX. \"%s\" غير صالح." + +#: .\core\validators.py:174 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "هذا العنوان %s لا يشير إلى مقطع فيديو QuickTime صالح." + +#: .\core\validators.py:178 +msgid "A valid URL is required." +msgstr "يجب ادخال عنوان صالح." + +#: .\core\validators.py:192 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"مطلوب شفرة HTML صالحة، الأخطاء على وجه التحديد هي:\n" +"%s" + +#: .\core\validators.py:199 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML مهيئة بصورة سيئة: %s" + +#: .\core\validators.py:209 +#, python-format +msgid "Invalid URL: %s" +msgstr "وصلة غير صالحة: %s" + +#: .\core\validators.py:213 +#: .\core\validators.py:215 +#, python-format +msgid "The URL %s is a broken link." +msgstr "الوصلة %s غير صحيحة." + +#: .\core\validators.py:221 +msgid "Enter a valid U.S. state abbreviation." +msgstr "أدخل اختصار ولاية أمريكية صحيحا." + +#: .\core\validators.py:236 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "انته إلى ما تقول! الكلمة %s غير مسموح بها هنا." +msgstr[1] "انتبه إلى ما تقول! الكلمتان %s غير مسموح بهما هنا." +msgstr[2] "انتبه إلى ما تقول! الكلمات %s غير مسموح بها هنا." +msgstr[3] "انتبه إلى ما تقول! الكلمات %s غير مسموح بها هنا." + +#: .\core\validators.py:243 +#, python-format +msgid "This field must match the '%s' field." +msgstr "هذا الحقل يجب أن يطابق الحقل '%s'." + +#: .\core\validators.py:262 +msgid "Please enter something for at least one field." +msgstr "الرجاء ادخال شيء ما في حقل واحد على الأقل." + +#: .\core\validators.py:271 +#: .\core\validators.py:282 +msgid "Please enter both fields or leave them both empty." +msgstr "الرجاء ادخال كلا الحقلين أن ترك كلاهما فارغا." + +#: .\core\validators.py:289 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "هذا الحقل يجب أن يعطي إذا كان %(field)s %(value)s" + +#: .\core\validators.py:301 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "هذا الحقل يجب أن يعطى إذا لم يكن %(field)s %(value)s" + +#: .\core\validators.py:320 +msgid "Duplicate values are not allowed." +msgstr "القيم المكررة غير مسموح بها." + +#: .\core\validators.py:343 +#, python-format +msgid "This value must be a power of %s." +msgstr "يجب أن تكون القيام من مضاعفات %s." + +#: .\core\validators.py:354 +msgid "Please enter a valid decimal number." +msgstr "الرجاء ادخال رقم عشري صالح." + +#: .\core\validators.py:356 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "رجاء أدخل رقم عشري صالح مكون من خانة واحدة على الأكثر." +msgstr[1] "رجاء أدخل رقم عشري صالح مكون من خانتين على الأكثر." +msgstr[2] "رجاء أدخل رقم عشري صالح مكون من %s خانات على الأكثر." +msgstr[3] "رجاء أدخل رقم عشري صالح مكون من %s خانة على الأكثر." + +#: .\core\validators.py:359 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "رجاء أدخل رقم عشري صالح يكون الجزء الصحيح منه مكونا من خانة واحدة على الأكثر." +msgstr[1] "رجاء أدخل رقم عشري صالح يكون الجزء الصحيح منه مكونا من خانتين على الأكثر." +msgstr[2] "رجاء أدخل رقم عشري صالح يكون الجزء الصحيح منه مكونا من %s خانات على الأكثر." +msgstr[3] "رجاء أدخل رقم عشري صالح يكون الجزء الصحيح منه مكونا من %s خانة على الأكثر." + +#: .\core\validators.py:362 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "الرجاء ادخال رقم عشري صالح تكون فيه خانة عشرية واحدة على الأكثر." +msgstr[1] "الرجاء ادخال رقم عشري صالح تكون فيه خانتان عشريتان على الأكثر." +msgstr[2] "الرجاء ادخال رقم عشري صالح تكون فيه %s خانات عشرية على الأكثر." +msgstr[3] "الرجاء ادخال رقم عشري صالح تكون فيه %s خانة عشرية على الأكثر." + +#: .\core\validators.py:372 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "تأكد من أن حجم الملف الذي قمت برفعه لا يقل عن %s بايت." + +#: .\core\validators.py:373 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "تأكد من أن الملف الذي قمت برفعه لا يزيد عن %s بايت." + +#: .\core\validators.py:390 +msgid "The format for this field is wrong." +msgstr "تنسيق هذا الحقل خاطئ." + +#: .\core\validators.py:405 +msgid "This field is invalid." +msgstr "هذا الحقل غير صحيح." + +#: .\core\validators.py:441 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "تعذر جلب أي شيء من %s." + +#: .\core\validators.py:444 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "الوصلة %(url)s أعادت ترويسة Content-Type الخاطئة '%(contenttype)s'." + +#: .\core\validators.py:477 +#, python-format +msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgstr "الرجاء اغلاق الوسم %(tag)s في سطر %(line)s. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\core\validators.py:481 +#, python-format +msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" +msgstr "بعض النص الذي يبدأ في سطر %(line)s غير مسموح به في هذا السياق. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\core\validators.py:486 +#, python-format +msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" +msgstr "\"%(attr)s\" في السطر %(line)s هي سمة غير صالحة. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\core\validators.py:491 +#, python-format +msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" +msgstr "\"<%(tag)s>\" في السطر %(line)s وسم غير صالح. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\core\validators.py:495 +#, python-format +msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" +msgstr "هنالك وسم في السطر %(line)s تنقصه سمة واحدة أو أكثر. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\core\validators.py:500 +#, python-format +msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" +msgstr "السمة \"%(attr)s\" في السطر %(line)s تمتلك قيمة غير صالحة. (يبدأ السطر هكذا \"%(start)s\".)" + +#: .\db\models\manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s مع هذا %(type)s موجودة بالفعل لأجل %(field)s." + +#: .\db\models\fields\related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "الرجاء ادخال %s صالح." + +#: .\db\models\fields\related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "افصل بين المعرفات بفواصل comma." + +#: .\db\models\fields\related.py:620 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "اضغط زر التحكم \"Control\", أو \"Command\" على أجهزة Mac لاختيار أكثر من واحد." + +#: .\db\models\fields\related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "الرجاء ادخال معرفات %(self)s صالحة، القيمة %(value)r غير صالحة." +msgstr[1] "الرجاء ادخال معرفات %(self)s صالحة، القيمتان %(value)r غير صالحة." +msgstr[2] "الرجاء ادخال معرفات %(self)s صالحة، القيم %(value)r غير صالحة." +msgstr[3] "الرجاء ادخال معرفات %(self)s صالحة، القيم %(value)r غير صالحة." + +#: .\db\models\fields\__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s بالحقل %(fieldname)s موجود بالفعل." + +#: .\db\models\fields\__init__.py:114 +#: .\db\models\fields\__init__.py:265 +#: .\db\models\fields\__init__.py:548 +#: .\db\models\fields\__init__.py:559 +#: .\forms\__init__.py:346 +msgid "This field is required." +msgstr "هذا الحقل مطلوب." + +#: .\db\models\fields\__init__.py:337 +msgid "This value must be an integer." +msgstr "هذه القيمة يجب أن تكون رقما صحيحا." + +#: .\db\models\fields\__init__.py:369 +msgid "This value must be either True or False." +msgstr "هذه القيمة يجب أن تكون إما صح أو خطأ." + +#: .\db\models\fields\__init__.py:385 +msgid "This field cannot be null." +msgstr "لا يمكن أن تكون قيمة هذا الحقل لا شيء." + +#: .\db\models\fields\__init__.py:568 +msgid "Enter a valid filename." +msgstr "أدخل اسم ملف صالح." + +#: .\forms\__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "تأكد من أن النص الذي أدخلته أقل من خانة واحدة." +msgstr[1] "تأكد من أن النص الذي أدخلته أقل من خانتين." +msgstr[2] "تأكد من أن النص الذي أدخلته أقل من %s خانات." +msgstr[3] "تأكد من أن النص الذي أدخلته أقل من %s خانة." + +#: .\forms\__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "الأسطر الجديدة غير مسموح هتا." + +#: .\forms\__init__.py:485 +#: .\forms\__init__.py:558 +#: .\forms\__init__.py:597 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "حدد خيارا صحيحا; '%(data)s' ليست ضمن %(choices)s." + +#: .\forms\__init__.py:661 +msgid "The submitted file is empty." +msgstr "الملف الذي قمت بارساله فارغ." + +#: .\forms\__init__.py:717 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "أدخل رقما صحيحا بين -32,768 و 32,767." + +#: .\forms\__init__.py:727 +msgid "Enter a positive number." +msgstr "أدخل رقما موجبا." + +#: .\forms\__init__.py:737 +msgid "Enter a whole number between 0 and 32,767." +msgstr "أدخل رقما صحيحا بين 0 و 32,767." + +#: .\template\defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "نعم،لا،ربما" + +#: .\utils\dates.py:6 +msgid "Monday" +msgstr "الاثنين" + +#: .\utils\dates.py:6 +msgid "Tuesday" +msgstr "الثلاثاء" + +#: .\utils\dates.py:6 +msgid "Wednesday" +msgstr "الأربعاء" + +#: .\utils\dates.py:6 +msgid "Thursday" +msgstr "الخميس" + +#: .\utils\dates.py:6 +msgid "Friday" +msgstr "الجمعة" + +#: .\utils\dates.py:7 +msgid "Saturday" +msgstr "السبت" + +#: .\utils\dates.py:7 +msgid "Sunday" +msgstr "الأحد" + +#: .\utils\dates.py:14 +msgid "January" +msgstr "يناير" + +#: .\utils\dates.py:14 +msgid "February" +msgstr "فبراير" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "March" +msgstr "مارس" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "April" +msgstr "ابريل" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "May" +msgstr "مايو" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "June" +msgstr "يونيو" + +#: .\utils\dates.py:15 +#: .\utils\dates.py:27 +msgid "July" +msgstr "يوليو" + +#: .\utils\dates.py:15 +msgid "August" +msgstr "أغسطس" + +#: .\utils\dates.py:15 +msgid "September" +msgstr "سبتمبر" + +#: .\utils\dates.py:15 +msgid "October" +msgstr "أكتوبر" + +#: .\utils\dates.py:15 +msgid "November" +msgstr "نوفمبر" + +#: .\utils\dates.py:16 +msgid "December" +msgstr "ديسمبر" + +#: .\utils\dates.py:19 +msgid "jan" +msgstr "" + +#: .\utils\dates.py:19 +msgid "feb" +msgstr "" + +#: .\utils\dates.py:19 +msgid "mar" +msgstr "" + +#: .\utils\dates.py:19 +msgid "apr" +msgstr "" + +#: .\utils\dates.py:19 +msgid "may" +msgstr "" + +#: .\utils\dates.py:19 +msgid "jun" +msgstr "" + +#: .\utils\dates.py:20 +msgid "jul" +msgstr "" + +#: .\utils\dates.py:20 +msgid "aug" +msgstr "" + +#: .\utils\dates.py:20 +msgid "sep" +msgstr "" + +#: .\utils\dates.py:20 +msgid "oct" +msgstr "" + +#: .\utils\dates.py:20 +msgid "nov" +msgstr "" + +#: .\utils\dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "يناير" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "فبراير" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "أغسطس" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "سبتمبر" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "أكتوبر" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "نوفمبر" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "ديسمبر" + +#: .\utils\timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "سنة" +msgstr[1] "سنتان" +msgstr[2] "سنوات" +msgstr[3] "سنة" + +#: .\utils\timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "شهر" +msgstr[1] "شهران" +msgstr[2] "شهور" +msgstr[3] "شهر" + +#: .\utils\timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "أسبوع" +msgstr[1] "أسبوعان" +msgstr[2] "أسابيع" +msgstr[3] "أسبوع" + +#: .\utils\timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "يوم" +msgstr[1] "يومان" +msgstr[2] "أيام" +msgstr[3] "يوم" + +#: .\utils\timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "ساعة" +msgstr[1] "ساعتان" +msgstr[2] "ساعات" +msgstr[3] "ساعة" + +#: .\utils\timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "دقيقة" +msgstr[1] "دقيقتان" +msgstr[2] "دقائق" +msgstr[3] "دقيقة" + +#: .\utils\translation.py:363 +msgid "DATE_FORMAT" +msgstr "" + +#: .\utils\translation.py:364 +msgid "DATETIME_FORMAT" +msgstr "" + +#: .\utils\translation.py:365 +msgid "TIME_FORMAT" +msgstr "" + +#: .\utils\translation.py:381 +msgid "YEAR_MONTH_FORMAT" +msgstr "" + +#: .\utils\translation.py:382 +msgid "MONTH_DAY_FORMAT" +msgstr "" + diff --git a/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/ar/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..02c1d67b587e2f42407cae325d949041676cdd6a GIT binary patch literal 1774 zcwTLi?`s@I7{|vX7w!40Mk@#^0}`~9je9AH@l3_@(m-o34Y{+`7lM=B>D?xKJ7IP= z^}si2ei-v!@U0DU_0lE{XRWP&fzY?&EQ0vT7rs&u@r5t^&dw%hz#}uC=b3qae4p9; zJT&kb!S7kD=dm7Oy@2)cLnQtE4i18Ufc@a#;A7xF;3MF%K65_=J`O$y=E0Z2W8i4U zNiYXagYSYD`b_?HZ~)xtGxcw0^}f#PeFswTdvF5$EvxfVzsWz@pY;Jhho;GXbN|DD z(eV@bB={@%1o&5Wp39k;kAo+)-kga$ld+OBbAAAR3NB=Ef96bne$ezkF=+I@i0v70 z7_`A6_#wDBXyPAa@jnilxqbol6YJS^-GiUjlp{xfTF-G}-bc?mL`)4`?_n%FNvc)r z&@*ZsPiMKh_oJEpl-|ZE8)=S^lIxPvGV^>k?{hjFk}~so;4%p#aJWy(4Ix5K8I8Oq zKlJ?cBIH4u;&Ud=nJ=70a-Ib-lPx;U=VfYCm~f=LTNA5s?`o0&G{w9B(3uRB46Q=w+;FS|wHPb0?xL z5liYcgy-;A!jqH*E_Hm*S)`;IV?1aQDQYnfH7|2FFjlP*%hZ^bo<@~Lv4lZt!t6~U zCAz=Q!-%IBvr;Uer*+P?E^!%pA}G?S1s2o=tzMeR&+#Q8BdZeDJ=c0Wu7_4l6ls3; zV$CW`&LYHHQ!L^|I%?ZvR$k)Mzr7miz__8@%b!Nv03pO83TyBbsj_C#c7)&aE0}&Hu@W zbh#rLm2;J|y)g?TcD^ivhzDrhT7tHSe-ypC^Rv1_o)s~R+BYd_V8j~bqGSc>Q?VQt=mP&J@iDU4IFIg+AHb?5;A997gb#fy*NeK zT6zX!lj<$m=$UJ&skWiEtM2NgE|NNkzoCU~C#!nO)Il1?J-FFJudZ<*6G}SjPO@U8 z>Y|z0Z<9|Dkeb(%4We$T+sO)1+f@JUr28&aH_=w#?WsNZ=2ena-G1}X$Lin=V%qo& z_tjlfbz7&=%-l&w+c549R$J}puW)d`54rbx2fGlyrDxX8O$>u)rP&+f5z+?fD;<8( MJz{(<4c~_R3um%(eE, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-07-06 23:50+0300\n" +"Last-Translator: Ahmad Alhashemi \n" +"Language-Team: Ahmad Alhashemi \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Arabic\n" +"X-Poedit-Country: Kuwait\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s متوفرة" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "اختيار الكل" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "إضافة" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "حذف" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s اختيرت" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "حدد خيارك أو خياراتك واضغط" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "مسح الكل" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "January February March April May June July August September October November December" +msgstr "يناير فبراير مارس إبريل مايو يونيو يوليو أغسطس سبتمبر أكتوبر نوفمبر ديسمبر" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "الأحد الأثنين الثلاثاء الأربعاء الخميس الجمعة السبت" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "أ أ ث أ خ ج س" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "الآن" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "الساعة" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "اختر وقتا ما" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "منتصف الليل" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 ص." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "الظهر" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "الغاء" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "اليوم" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "التقويم" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "يوم أمس" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "الغد" + diff --git a/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/bn/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..672e73e816f8a98d3364f97bd6acc464e3d11f2b GIT binary patch literal 25779 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

      e%&Muptzo8~p|3F^TV zb&G1kN1NdZ#Z)9VpX6TSQQ?QnvYE=(B3ubjkr+5ncw(0EDOs#R$ zlp`a-ouvB&8{gou_h?Wl#gEie9=#=!on+XW5FQPBowI?&9DA73LEC-*xHkE?E@zCA zo1y*}g=Jwu-1$@^JW(>lZI!C0hom~|dcwNzK6}#DOi&0l7|70?b;X1)@;jxil^)g6 zy|Jwn&_JFtv!4V{^+|=MFe@9V@Ws8@ zcFgcFZvA;I>ijA8c|poL)0fR}#ColHR-~f=P$w+k%-7tDIyyC0OohpdZpedYj+HtY zEVRix(><^m*9-OgWDw)q()FREsm}Pl7E>@Wi$#(4CP3^zxR^HqLdDqP+_E~`A?l+N zbETakyvS>fdWQHCCc_!HXekHrBqqiK!A`8nW4>3u%o)|NhE&Gu3v0x zlx<}~FWR*@RRR&_#<}c27uLN&dg88%S+nj(9v0r19wAlJ<%1-j#Iu&VJ)D%_ukE3O z$*DoYx-b&~vyUM4jKw@8##Krwb3`XjLt+DB@6aX*1Y(Ro6ird1kbPCpO;YN2JA6VO zFwW8==Et2Xsv?OU2W8dVJZ1A!(K4rzPjoilp~zzUle z6pJz01S>foT}+_rX2kV>K4I)sMb2X$3tlIcxXM`|$V0jz4|a)EjVXou@Z-iTnAoK~ z^-19-TJD*WHD=_;Y+{`4m?>E|CJTj&zda+qCiI^TQinEqgrxr>k3H1joR%_QOdzS{ zFu%c(BBL

      VFFI1Z-K;zkSG* zauTr5vMv@8oMKuNOgSBiZO3AP(qx~AR-!e*`RG&DfFUvP=k;__qlv-IoyY|Y%Z|Bj z7bJ@2iBn|MoJFSmm%!H4zV*3Dt}876r)$#<0Pqf)u!wg{($0T+?63OuNnh@%ADCO+S+dR!pqWUkJZD3 eY=(ljUDHSKE00vd)nZnda+a)Sb&y6Zp!^U0__-, 2005. +# +# +msgid "" +msgstr "" +"Project-Id-Version: Django CVS\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:12+0200\n" +"PO-Revision-Date: 2005-11-12 20:05+0530\n" +"Last-Translator: Baishampayan Ghose \n" +"Language-Team: Ankur Bangla \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +#, fuzzy +msgid "object ID" +msgstr "বস্তু আই.ডি." + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +#, fuzzy +msgid "comment" +msgstr "অভ্যন্তরস্থ বস্তু" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +#, fuzzy +msgid "IP address" +msgstr "ই-মেল ঠিকানা" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "অভ্যন্তরস্থ বস্তু" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +#, fuzzy +msgid "Content object" +msgstr "অভ্যন্তরস্থ বস্তু ধরন" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" + +#: contrib/comments/models.py:168 +#, fuzzy +msgid "person's name" +msgstr "প্রথম নাম" + +#: contrib/comments/models.py:171 +#, fuzzy +msgid "ip address" +msgstr "ই-মেল ঠিকানা" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "" + +#: contrib/comments/models.py:234 +#, fuzzy +msgid "score date" +msgstr "শেষ তারিখ" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/models.py:265 +#, fuzzy +msgid "flag date" +msgstr "চ্যাপ্টা পাতা" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "ব্যবহারকারী" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "ব্যবহারকারী" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "" + +#: contrib/comments/models.py:278 +#, fuzzy +msgid "deletion date" +msgstr "অধিবেশন তথ্য" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "" + +#: contrib/comments/views/karma.py:23 +#, fuzzy +msgid "Invalid comment ID" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "ব্যবহারকারীর নাম:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "পাসওয়ার্ড:" + +#: contrib/comments/templates/comments/form.html:6 +#, fuzzy +msgid "Forgotten your password?" +msgstr "আমার পাসওয়ার্ড পরিবর্তন করুন" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "বাইরে যান" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +#, fuzzy +msgid "Comment:" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +#, fuzzy +msgid "Preview comment" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/comments/templates/comments/freeform.html:4 +#, fuzzy +msgid "Your name:" +msgstr "ব্যবহারকারীর নাম" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

      By %s:

      \n" +"
        \n" +msgstr "" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "" + +#: contrib/admin/filterspecs.py:110 +#, fuzzy +msgid "Today" +msgstr "সোমবার" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "" + +#: contrib/admin/filterspecs.py:143 +#, fuzzy +msgid "No" +msgstr "নভে." + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "কাজের সময়" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "বস্তু আই.ডি." + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "বস্তু repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "কাজের ফ্ল্যাগ" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "বার্তা পরিবর্তন" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "কার্যবিবরণী এন্ট্রি" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "কার্যবিবরণী এন্ট্রি সমুহ" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "প্রবেশ করুন" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "" + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" + +#: contrib/admin/views/main.py:226 +#, fuzzy +msgid "Site administration" +msgstr "জ্যাঙ্গো পরিচালনা" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "" + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "" + +#: contrib/admin/views/main.py:290 +#, fuzzy, python-format +msgid "Add %s" +msgstr "যোগ করুন" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "" + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "" + +#: contrib/admin/views/main.py:338 +#, fuzzy, python-format +msgid "Changed %s." +msgstr "পরিবর্তন" + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "" + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "" + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "" + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:392 +#, fuzzy, python-format +msgid "Change %s" +msgstr "পরিবর্তন" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "" + +#: contrib/admin/views/main.py:533 +#, fuzzy, python-format +msgid "Change history: %s" +msgstr "পাসওয়ার্ড পরিবর্তন করুন" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "" + +#: contrib/admin/views/doc.py:281 +#, fuzzy +msgid "Date (without time)" +msgstr "কাজের সময়" + +#: contrib/admin/views/doc.py:282 +#, fuzzy +msgid "Date (with time)" +msgstr "তারিখ/সময়" + +#: contrib/admin/views/doc.py:283 +#, fuzzy +msgid "E-mail address" +msgstr "ই-মেল ঠিকানা:" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "" + +#: contrib/admin/views/doc.py:285 +#, fuzzy +msgid "Decimal number" +msgstr "ডিসেম্বর" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "" + +#: contrib/admin/views/doc.py:293 +#, fuzzy +msgid "Phone number" +msgstr "একটি গোটা সংখ্যা ঢোকান।" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "ইউ.আর.এল" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "পাসওয়ার্ড পরিবর্তন করুন" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "বাড়ি" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "ইতিহাস" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "তারিখ/সময়" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "ব্যবহারকারী" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "কাজ" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"এই বস্তুটি একটি পরিবর্তন ইতিহাস নেই। এইটি এই অ্যাডমিন স্থান দ্বারা সম্ভবত যোগ করা " +"হয় নি।" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "জ্যাঙ্গো স্থান অ্যাডমিন" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "জ্যাঙ্গো পরিচালনা" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "সার্ভার ত্রুটি" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "সার্ভার ত্রুটি (৫০০)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "সার্ভার ত্রুটি(৫০০)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"একটি ত্রুটি আছে। এইটি স্থান পরিচালককে ই-মেল দ্বারা প্রতিবেদন করা হয়েছে এবং খুব " +"তাড়াতাড়ি মেরামত করা হবে। আপনার ধৈর্য্যের জন্য ধন্যবাদ।" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "পাতা খুঁজে পাওয়া গেল না" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "আমরা দুঃখিত, কিন্তু আবেদনকৃত পাতা খুঁজে পাওয়া গেল না।" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "যোগ করুন" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "পরিবর্তন" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "আপনার কোন কিছু সম্পাদনা করতে অনুমতি নেই।" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "সাম্প্রতিক কাজ" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "আমার কাজ" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "কিছুই পাওয়া যাচ্ছে না" + +#: contrib/admin/templates/admin/change_list.html:11 +#, fuzzy, python-format +msgid "Add %(name)s" +msgstr "যোগ করুন" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "আপনি কি আপনার পাসওয়ার্ড ভুলে গেছেন?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "স্বাগত," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"%(object_name)s '%(object)s' মুছে ফেললে তা সম্পর্কিত বস্তুও মুছে ফেলা হবে কিন্তু " +"আপনার অ্যাকাউন্টে বস্তুর নিম্নলিখিত ধরন মুছে ফেলার অনুমতি নেই:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"%(object_name)s\" %(object)s\" মুছে ফেলতে আপনি কি নিশ্চিত? নিম্নলিখিত সম্পর্কিত " +"বস্তুর সব মুছে ফেলা হবে:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "হ্যাঁ, আমি নিশ্চিত" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:7 +#, fuzzy +msgid "Save" +msgstr "সক্রিয়" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "পাসওয়ার্ড পরিবর্তন" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "পাসওয়ার্ড পরিবর্তন সফল" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "আপনার পাসওয়ার্ড পরিবর্তন করা হয়েছিল।" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "পাসওয়ার্ড রিসেট" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"আপনি কি আপনার পাসওয়ার্ড ভুলে গেছেন? আপনার ই-মেল ঠিকানা নিচে দিন এবং আমরা আপনার " +"পাসওয়ার্ড রিসেট করে এবং আপনাকে নতুন এক পাসওয়ার্ড ই-মেল করে দেব।" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "ই-মেল ঠিকানা:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "আমার পাসওয়ার্ড রিসেট করুন" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "এই ওয়েব স্থানে আপনার কিছু সময় খরচ করার জন্য ধন্যবাদ।" + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "আবার প্রবেশ করুন" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "পাসওয়ার্ড রিসেট সফল" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"আমরা আপনাকে একটি নতুন পাসওয়ার্ড আপনার দেওয়া ই-মেল ঠিকানায় পাঠিয়ে দিয়েছি। আপনার " +"তা খুব তাড়াতাড়ি পাওয়া উচিত।" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"দয়া করে আপনার পুরনো পাসওয়ার্ড ঢোকান, নিরাপত্তার জন্য, এবংএর জন্য তারপর আপনার নতুন " +"পাসওয়ার্ড দুবার ঢোকান যাতে আমরা সঠিকভাবেতে তার বৈধতা পরীক্ষণ করতে পারি।" + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "পুরনো পাসওয়ার্ড:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "নতুন পাসওয়ার্ড:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "পাসওয়ার্ড দৃঢ়তরভাবে প্রতিপন্ন করুন:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "আমার পাসওয়ার্ড পরিবর্তন করুন" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "আপনি এই ই-মেলটি পাচ্ছেন কারণ আপনি একটি পাসওয়ার্ড রিসেট অনুরোধ করেছেন" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "%(site_name)s আপনার ব্যবহারকারী অ্যাকাউন্টের জন্য" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "আপনার নতুন পাসওয়ার্ড: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "এই পাতাটিতে গিয়ে এই পাসওয়ার্ডটি পরিবর্তন করতে মুক্ত অনুভব করুন:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "আপনার ব্যবহারকারীর নাম, যদি আপনি ভুলে গিয়ে থাকেন:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "আমাদের স্থান ব্যবহার করার জন্য ধন্যবাদ!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s দল" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

        To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

        \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +#, fuzzy +msgid "Show object ID" +msgstr "বস্তু আই.ডি." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "" + +#: contrib/admin/templates/widget/file.html:3 +#, fuzzy +msgid "Change:" +msgstr "পরিবর্তন" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "রিডাইরেক্ট করা" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"এইটি একটি পরম পাথ হওয়া উচিত, ক্ষেত্র নাম বাদ দিয়ে। উদাহরণ: '/events/search / '।" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "রিডাইরেক্ট কর" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"এইটি হয় একটি পরম পাথ হতে পারে (যেমন উপরে) অথবা 'http:// এর সঙ্গে শুরু হওয়া " +"একটি পূর্ণ ইউ.আর.এল ।" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "রিডাইরেক্ট" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "রিডাইরেক্ট করে" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"উদাহরণ: '/about/contact / '। প্রধান এবং অনুসরণ করা স্ল্যাশ যেন নিশ্চিত ভাবে থাকে।" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "শিরোনাম" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "অভ্যন্তরস্থ বস্তু" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "মন্তব্য সক্রিয় করুন" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "ছাঁদ নাম" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"উদাহরণ: 'flatfiles/contact_page '। যদি এইটি দেওয়া না থাকে তাহলে সিস্টেম " +"'flatfiles/default ব্যবহার করবে।" + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "নিবন্ধন প্রয়োজনীয়" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"যদি এইটি টিক্ করা থাকে তাহলে শুধুমাত্র প্রবেশ করা ব্যবহারকারী পাতা দেখতে সক্ষম হবে।" + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "চ্যাপ্টা পাতা" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "চ্যাপ্টা পাতাগুলো" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "নাম" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "ছদ্বনাম" + +#: contrib/auth/models.py:17 +#, fuzzy +msgid "permission" +msgstr "অনুমতি" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +#, fuzzy +msgid "permissions" +msgstr "অনুমতি" + +#: contrib/auth/models.py:29 +#, fuzzy +msgid "group" +msgstr "গ্রুপ" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +#, fuzzy +msgid "groups" +msgstr "দল" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "ব্যবহারকারীর নাম" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "প্রথম নাম" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "শেষ নাম" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "ই-মেল ঠিকানা" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "পাসওয়ার্ড" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr " অবস্থা" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "নিশ্চয় করে যে ব্যবহারকারী এই অ্যাডমিন স্থানে প্রবেশ করতে পারে কিনা।" + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "সক্রিয়" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "সুপারইউজার অবস্থা" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "শেষ প্রবেশ" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "যোগাদান করার তারিখ " + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"নিজ হাতে বরাদ্দকৃতে অনুমতি ছাড়া এই ব্যবহারকারীটি যে সব গ্রুপে আছে তার অনুমতিও পাবে।" + +#: contrib/auth/models.py:67 +#, fuzzy +msgid "user permissions" +msgstr "অনুমতি" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "ব্যবহারকারী" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "ব্যবহারকারী" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "ব্যক্তিগত তথ্য" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "অনুমতি" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "গুরুত্বপূর্ণ তারিখ" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "দল" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "বার্তা" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" + +#: contrib/contenttypes/models.py:25 +#, fuzzy +msgid "python model class name" +msgstr "পাইথন মডিউলের নাম" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "অভ্যন্তরস্থ বস্তু ধরন" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "অভ্যন্তরস্থ বস্তু ধরন" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "অধিবেশন চাবি" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "অধিবেশন তথ্য" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "শেষ তারিখ" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "অধিবেশন" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "সেশন" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "ক্ষেত্র নাম" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "প্রদর্শিত নাম" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "স্থান" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "স্থান" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "সোমবার" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "মঙ্গলবার" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "বুধবার" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "বৃহস্পতিবার" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "শুক্রবার" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "শনিবার" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "রবিবার" + +#: utils/dates.py:14 +msgid "January" +msgstr "জানুয়ারী" + +#: utils/dates.py:14 +msgid "February" +msgstr "ফেব্রুয়ারী" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "মার্চ" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "এপ্রিল" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "মে" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "জুন" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "জুলাই" + +#: utils/dates.py:15 +msgid "August" +msgstr "অগাস্ট" + +#: utils/dates.py:15 +msgid "September" +msgstr "সেপ্টেম্বর" + +#: utils/dates.py:15 +msgid "October" +msgstr "অক্টোবর" + +#: utils/dates.py:15 +msgid "November" +msgstr "নভেম্বর" + +#: utils/dates.py:16 +msgid "December" +msgstr "ডিসেম্বর" + +#: utils/dates.py:19 +msgid "jan" +msgstr "" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +#, fuzzy +msgid "may" +msgstr "মে" + +#: utils/dates.py:19 +msgid "jun" +msgstr "" + +#: utils/dates.py:20 +msgid "jul" +msgstr "" + +#: utils/dates.py:20 +msgid "aug" +msgstr "" + +#: utils/dates.py:20 +msgid "sep" +msgstr "" + +#: utils/dates.py:20 +msgid "oct" +msgstr "" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "জানু." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "ফেব্রু" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "অগা." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "সেপ্টে." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "অক্টো." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "নভে." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "ডিসে." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:15 +#, fuzzy +msgid "day" +msgid_plural "days" +msgstr[0] "মে" +msgstr[1] "মে" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:17 +#, fuzzy +msgid "minute" +msgid_plural "minutes" +msgstr[0] "স্থান" +msgstr[1] "স্থান" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "বাংলা" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "চেক" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "ওয়েল্শ" + +#: conf/global_settings.py:40 +#, fuzzy +msgid "Danish" +msgstr "স্প্যানিশ" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "জার্মান" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "ইংরেজী" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "স্প্যানিশ" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "ফরাসী" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "গ্যালিসিয়" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "ইতালিয়" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "নরওয়েজিয়" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "ব্রাজিলীয়" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "রোমানীয়" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "রুশ" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "স্লোভাক" + +#: conf/global_settings.py:58 +#, fuzzy +msgid "Slovenian" +msgstr "স্লোভাক" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "সার্বিয়ান" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:61 +#, fuzzy +msgid "Ukrainian" +msgstr "ব্রাজিলীয়" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "সরলীকৃত চীনা" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "এই মানটি শুধু মাত্র অক্ষর, অংক, এবং আন্ডারস্কোর (_) হতে পারবে।" + +#: core/validators.py:64 +#, fuzzy +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "এই মানটি শুধু মাত্র অক্ষর, অংক, আন্ডারস্কোর (_) এবং স্ল্যাশ হতে পারবে।" + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "বড় হাতের অক্ষর এখানে ঢোকাতে পারবেন না।" + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "ছোটহাতের অক্ষর এখানে ঢোকাতে পারবেন না।" + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "কমা দ্বারা আলাদা করে শুধু মাত্র অংক ঢোকান।" + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "কমা দ্বারা আলাদা করে বৈধ ই-মেল ঠিকানা ঢোকান।" + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "দয়া করে একটি বৈধ IP ঠিকানা ঢোকান।" + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "ফাঁকা মান এখানে ঢোকাতে পারবেন না।" + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "অংক বিহীন অক্ষর এখানে ঢোকাতে পারবেন না।" + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "এই মানটি শুধু মাত্র অংকের দ্বারা গঠিত হতে পারে না।" + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "একটি গোটা সংখ্যা ঢোকান।" + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "এখানে শুধু মাত্র অক্ষর ঢোকাতে পারবেন ।" + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "YYYY-MM-DD ফরম্যাটে একটি বৈধ তারিখ ঢোকান।" + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "HH:MM ফরম্যাটে একটি বৈধ সময় ঢোকান।" + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "YYYY-MM-DD.HH:MM ফরম্যাটে একটি বৈধ তারিখ/সময় ঢোকান।" + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "একটি বৈধ ই-মেল ঠিকানা ঢোকান।" + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"একটি বৈধ চিত্র আপলোড করুন। যে ফাইল আপনি আপলোড করলেন তা হয় একটি চিত্র নয় অথবা " +"একটি খারাপ চিত্র।" + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "%s ইউ.আর.এল-এ একটি বৈধ চিত্র নেই।" + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "ফোন নম্বর XXX-XXX-XXXX ফরম্যাটে অবশ্যই হতে হবে। \"%s\" অবৈধ।" + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "%s ইউ.আর.এল-এ একটি বৈধ QuickTime ভিডিও পাওয়া গেল না।" + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "একটি বৈধ ইউ.আর.এল জরুরি।" + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"বৈধ এইচটিএমএল প্রয়োজনীয়। ত্রুটিগুল হল:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "খারাপভাবে গঠিত এক্সএমএল: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "অবৈধ ইউ.আর.এল: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "ইউ.আর.এল %s একটি ভাঙা সংযোগ।" + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "একটি বৈধ U S. রাজ্য abbreviation ঢোকান।" + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "মুখ সামলে! %s এখানে ব্যাবহার করতে পারবেন না।" +msgstr[1] "মুখ সামলে! %s এখানে ব্যাবহার করতে পারবেন না।" + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "এই ক্ষেত্রটি '%s' ক্ষেত্রের সঙ্গে অবশ্যই মিলতে হবে।" + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "দয়া করে অন্তত এক ক্ষেত্রেতে কিছু জিনিষ ঢোকান।" + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "দয়া করে উভয় ক্ষেত্রে ঢোকান অথবা তাদেরকে উভয় ফাঁকা ছেড়ে চলে যান।" + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "এই ক্ষেত্রেটি অবশ্যই দেওয়া হবে যদি %(field)s %(value)s হয়" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "এই ক্ষেত্রেটি অবশ্যই দেওয়া হবে যদি %(field)s %(value)s না হয়" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "নকল মান অনুমতি দেওয়া হল না।" + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "এই মানটি %sএর একটি গুন অবশ্যই হতে হবে।" + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "দয়া করে একটি বৈধ দশমিক সংখ্যা ঢোকান।" + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "দয়া করে একটি বৈধ দশমিক সংখ্যা ঢোকান যার অক্ষরের সংখ্যা %s থেকে কম হবে।" +msgstr[1] "দয়া করে একটি বৈধ দশমিক সংখ্যা ঢোকান যার অক্ষরের সংখ্যা %s থেকে কম হবে।" + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "দয়া করে একটি বৈধ দশমিক সংখ্যা ঢোকান যার দশমিক স্থান %s থেকে কম হবে।" +msgstr[1] "দয়া করে একটি বৈধ দশমিক সংখ্যা ঢোকান যার দশমিক স্থান %s থেকে কম হবে।" + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "আাপনার আপলোড করা ফাইল যেন অন্তত %s বাইট বড় হয় তা নিশ্চিত করুন।" + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "আাপনার আপলোড করা ফাইল যেন %s বাইটের থেকে বড় না হয় তা নিশ্চিত করুন।" + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "এই ক্ষেত্রের জন্য ফরম্যাটটি ভূল।" + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "এই ক্ষেত্রেটি অবৈধ।" + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "%s থেকে কোন কিছু গ্রহন করতে পারলাম না।" + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"ইউ.আর.এল %(url)s অবৈধ Content-Type শিরোনাম নিয়ে '%(contenttype)s ফিরে আসল।" + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"দয়া করে লাইন %(line)s থেকে খোলা %(tag)s বন্ধ করুন। (\"%(start)s\"এর সঙ্গে লাইন " +"আরম্ভ।)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"লাইন %(line)s এই প্রসঙ্গে কিছু শব্দ লেখার অনুমতি দেওয়া গেল না। (\"%(start)s\"এর " +"সঙ্গে লাইন আরম্ভ।)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"লাইন %(line)s\"%(attr)s\"একটি অবৈধ বৈশিষ্ট্য। (\"%(start)s\"এর সঙ্গে লাইন আরম্ভ।)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +" %(line)s লাইনে \"<%(tag)s>\" একটি অবৈধ ট্যাগ। (\"%(start)s\"এর সঙ্গে লাইন " +"আরম্ভ।)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"লাইন %(line)s একটি ট্যাগে এক অথবা আরও বেশি প্রয়োজনীয় বিশিষ্ট্যাবলী নেই। (\"%" +"(start)s\"এর সঙ্গে লাইন আরম্ভ।)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"\"%(attr)s\"বৈশিষ্ট্যয় লাইন %(line)s একটি অবৈধ মান আছে । (\"%(start)s\"এর সঙ্গে " +"লাইন আরম্ভ।)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +#, fuzzy +msgid "This field is required." +msgstr "এই ক্ষেত্রেটি অবৈধ।" + +#: db/models/fields/__init__.py:337 +#, fuzzy +msgid "This value must be an integer." +msgstr "এই মানটি %sএর একটি গুন অবশ্যই হতে হবে।" + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "এই মানটি %sএর একটি গুন অবশ্যই হতে হবে।" + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "এই ক্ষেত্রেটি অবৈধ।" + +#: db/models/fields/__init__.py:562 +#, fuzzy +msgid "Enter a valid filename." +msgstr "একটি বৈধ ই-মেল ঠিকানা ঢোকান।" + +#: db/models/fields/related.py:43 +#, fuzzy, python-format +msgid "Please enter a valid %s." +msgstr "দয়া করে একটি বৈধ IP ঠিকানা ঢোকান।" + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr "একাধিক আই.ডি.কমার দ্বারা আলাদা করুন।" + +#: db/models/fields/related.py:581 +#, fuzzy +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"একের চেয়ে বেশি নির্বাচন করতে \"Control\" অথবা ম্যাক-এ \"Command\" চেপে ধরে " +"রাখুন।" + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:385 +#, fuzzy +msgid "Line breaks are not allowed here." +msgstr "ছোটহাতের অক্ষর এখানে ঢোকাতে পারবেন না।" + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "" + +#: forms/__init__.py:699 +#, fuzzy +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "একটি গোটা সংখ্যা ঢোকান।" + +#: forms/__init__.py:708 +#, fuzzy +msgid "Enter a positive number." +msgstr "একটি গোটা সংখ্যা ঢোকান।" + +#: forms/__init__.py:717 +#, fuzzy +msgid "Enter a whole number between 0 and 32,767." +msgstr "একটি গোটা সংখ্যা ঢোকান।" + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "" + +#, fuzzy +#~ msgid "Comments" +#~ msgstr "মন্তব্য সক্রিয় করুন" + +#~ msgid "label" +#~ msgstr "লেবেল" + +#~ msgid "package" +#~ msgstr "প্যাকেজ" + +#~ msgid "packages" +#~ msgstr "প্যাকেজ" + +#, fuzzy +#~ msgid "count" +#~ msgstr "অভ্যন্তরস্থ বস্তু" diff --git a/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..00cc135c2d7dd7151c917b59e71252c4c359d700 GIT binary patch literal 31909 zcwV)937i~PdFLD31S4Y15n>x~QkEn44C-mk$cN;SY-uEoG$ZR6X-1cgBv*G=_mrl( zs#V9#v}ALGIcx~T4v>Tp*kB+DBsRG**Lt~@uq;PHf&&o=V2j{{$UuTwmi>Q69X*FE zv&sBa{nfkQ``-7y>wTrKKl_4v4E|o%W6TQxy`MMc^7D*&;8MeX16LSxH_8S9Cz1Z- za$|M_KL1K%MgXS)uLivDN@@Qsz~=&<_bbL!0WSrlu1*5}9N^u6=L7yGAcivU(EL9D zd>Y^fwfw`H{y5hM+PXYWM;6;Ex{1qAhf)#iG*RGKMZUFoRz%77J1w6Du z#=K?+l@QRYm=UTvL00{Dy<8UqqCR{;(HZU@9abMl2U?|WY;{X76T2>4HcHvv9(rQpF{DR^iAt_2KM z3clXEQt*ENN}11x0cQaJW~Jb1XIbE6S=!&C={g{egPAT19(>J@6`m~1dS4E>1@Luc znaAhLGVYhlGVa$keHw5Z;A3UMzvO?RU+C@|n*aTN>G%I=x~C%Z zd|E~FpA9$+cnRPH;33U_e?|7`{))`^%N02X4^^a}M*t(h?*W!*y{nA54)9LEYXKiz zCF}mtDmj-w1;iB0`2&K-!2#)a`+%%-&w$X=iv|R5#|C5_Ex=y{d?_GMZr(B=^z`6> z;N^=0GT*-+kac}S)8848dH$>R`|ks?|4+f@W6GvHD0Fnop!EMn&A)e0`uXcYncvqn z{jEV6_g@AD&p#Oyy1!_(u9Wzag#N&?l-KF^Zpi1zfbGmuj#*BBlv!BjquG+ zuMs?aZH=t^-`5B}&R;8h=%Tf9zOGm+=lhv2DH3@$Ktn z{PzK(EAz?qA|HEh5I(l&1}RT&kbQqM;6A|1Fc};r699q)%sT+@2mJDlGQT@E$hhy? zAbjyJHVA!uVS}8{3pN@9QDoL_lyhfq6n^UgjsbpRqtM^Q!!k~DSopwOhDHAU#jx<< zza195^lXxS*tkjX{l-l~7jN4n_%SU7$D@WwKt{Rd4Zx|7I%L-4_{zYT;BE7m^m|}T=&!Er+%f5| zrEqpk@vY@|jmi4nG$!-;4K087nBe2SF*&Cn)%IUe_zi{stndd4f2#0l+XX&H;pN+9 zoYHpbcVN4WbEB4T-!6L4+JoLe{ZnLhx|Igz%@G6Ee@ETJCB;!G!E< zJR$46UDJ1JfA>rXKYrJQ(EV2@WM9r``NfkWXI?NV{C1tD4^2uxbCa^aFPoHhZ=V#o z@RyS!*Zy%*)_>tXS;z8yvj2Viq@Q2kC;G>q>=XU(U-k*#UcO)W^O5}`fA82Y^5pmT zOTCBo%QzPwka?{Egeq?K9T0u$68sDw5}t92WST!-D@y z4@>=#!!i%&u=Lkb_>#k7PrOOfcOMqIx%aT_^Mi+FJ`W$3`TtP+|H)y&(|JcE{q!T! z|1*z>Jh=3Tv@ahK{b2lv(93N{L~i`S5wSl%dPMs9&JpSVu_J=F$Bzg;h?&v({pF+5 zZ~sxj%kWV-rw5M8xtP;>uR1FG_-k7K?MG!@_Z<~K%Y#SNe{!qv$&-Mu1AORKu@@S) z%=h(x?*#laz+VMCR24q@9YCm>=J_=_r-y1%?@fU32fVs2ev5C^WuE&T=sbX)BXsgX zN9Orgj?nv89XXHRaRd(+H01nT)DV2GY>521x*_Fj8iJpV4LRpq8`8eokp6CKNdI?g z`EN9YKK`^Jbnv5w?AK*Y>F<}C(qE}5e7L_U`?I?#^Q|_e|C4|L;5|*j=ToMn-X&8q z&lgTff2#o#z;#n%kKd>HKbn$to##s0a%CQUuB@-($~;!PV&|=Og?>7K(|~VxML&Pq zF|ijm9uq&soySDa|Hv_!?_+>CTIT1cRX?8=`SF%%p{I{bvub9(HZA*ql_z}d)t=z% z4*~B6{H!Pa+}e_HKh%=*b_VbP!0UX>oyPOUuDd)CJ$7{<{Bma?`u}SKvFkq_h`jkB zAi6L;ZRsy)3q5|YE$87Ej|&_=F8kpfm-W5jxYU2}xY#344W++ZLpe9oq2MzN1&_0u zzCD!vdU+`ObayEI|5hk?|CHu`R{Q%>D0bA>L!p;zBK6-yDxV{nPfg)lBjJOei)5d^ z9*I9}do1$ym9gyi4`b>7r?Jd$St953nThb9=OseFmnXu1`V-l&?TPG*o5=ZnRU&?s zuL43nfI4wWd9$Vk@;OYD|onOR>t272zANqm=*cIYgX{#D0FAl zzcMTRzjRjSd*`gIAUC;YWE zC+oa!PUvZ`)@#m5yUv`9cl(^I=MHWE);Ssfy>mjxAJXz~YW@%Agbpt3$ht4;2!D7^ zN7ivgNBS)*yiUtEc4Yp$Ix>D;;gmwJBmK8KGXGa-{Wo`nPTs5OKkCSOKB?^^Gtj5~Q!)^qDgS%2%K zjQ@(0f`_|K$~@n7QuT$Cg2(rtl=*$^q~PU?CuQFrI;r-;NukqkYd=3ZDeHdrZBl;K zZPH))Ho?o)w+S6=x=rRWahuG`y-oIKM$2D(n~e8HEr086axQ)jKR*P#9q=kVpU3+@ z;(>ad{jEg)OM6V$Ux@Sz3whr&GGF@pmckth$xnQKUe)}%5grFoVLpJT(<5_zGoICW z2Wgn!!1DvGpXfN3Y5MilwnyxcKQ$sp-ioJ%yyxJ-mYQ4f{EgPX2M@%G87j<|d^INu z>Hom<1|vN4ziZwOEx*>t9vtcspCjoJ|C%>t{EqjCJgoMJ+;~*WE<*b0c%Fyn-AKPg z^NFYZF`f?^k+&bl^MCRDijKKX;fnyDi|1K*KCW$-(L8$O99>&j!}sz09-g;r`hOa+ z8OvH{qDN?${{BhfS|f7mRe1KG9OTA+rV8F|t@Df?@fk5+G$Ic-X_>EmlWp)vMrizR zwd@Wp`$fP%Frrt#$K-z`cdtj;cL2YLC+ZOyNq;xs{fM@|3~-CSzd+$Jnv=fMd7Q@k zr?u@`y!ZBqAMb0LH;nYdfO`RN(Y#+(_qBi+jX|+6?%2fKHF-n%ou-RD8D8=uptdE8tfE|5u?d*qS+IM8;Zppq`r_Y1tao|Ev*x zo_v*;Yd@>;jP}UA&n(m`s-`*$Py@FiOR zr&_*2)1*sXf#+R#-biWWe^cvx63SpzZ08Q#ftFhnrwS{*7g6BM>pVlL`DgC{&Q2%6)=u|grn}eGF5rr3O{{2)|==T$N+CAdG ze+l510Po6+S)QeTUp8VN6#qybt}LYAhG%`Df3mm8wi?FsIG(TK`87Q6!}HHthjjfH z7y6j%5r4yTHN6XIsAlHB;rV&(oBRWVcz3kU6?nf7a6_S=kKx&kd>hXrns<}7f2zXQ z06tUS|5EFa59AI!-^TN2Mr_3!wGQd8q~m@@>t19Q{*jFSb3AX+^vm#AX#b9R^=?=A z6op?`NV+*pYrgZLzkkEihxX4d;B_~i{doRf)4z-7XpiWZWJ~=6p2JA5!1F1*4*+iO z5&fV3F2MWq@q85TzlG;_@SHYcSCDOFbgakl{Gqln%htE8nrBDR4a=t-uf6=n$-r{` zD7HP%ss_Py%MPbKCyvTiJ+zxvJmpxP>rY#8V0D5d%<4qT;vn#B#P;i!U9Veiys&k}+7--Fo>jB`Ws&1KwU}EL=3q6#poLyKR@-hmm?c+;+}NpD zXdXLA+p!e|EyoHPR2VtSifgb!=Xm0V4v&K=R2^%^b!J_^nf0V&Shb+lPGTpt>Q3Y~ zeLHsQ%k0Qn?)u2|ZEv~N3l!yOh&oa1pkck``fe15b_^`I4Iar39lwNmB^cRlEaUoF z|8B9k@u-Eb29RQk;F^(!#vW?yvU6ZVQ~Pu$;%-k^F6+tjC}@f1425S=!4XnUb! z*E^Oo2XsVM1LzN3DkIc=f)hLcO$O-)=ecy5S0%-`y9riE2UU^wllknRc(* zL5n1EfHAXv>gpReAsr2^Uxn9Y>k|(c9i}xcy6l!4Mbt-td`z;1j#D}*pf`GAd<t2U;tc7_U+___jX1U4nn^>ONR5(wR@Ep)h?+DNVVx9~WM&ef6i=4hii_20- zwRQpJs1({KT+g+Avw6z)n~nk+Q{O-kI8B|QLRO1-KordeVLi>7mmX3mnB6?(Fek#k zRD(G>n1m1foVFbj|FGzoH?fmkfL<}hz;C!=E1Ns##5ABBEhH1K zZqc@f4k*!?aftt69UxfdRJ^z(1c!;e4w|2EYEx#*@Z`wk*v^roTX*l>IXubfuJ;3D zlcPta#=adp%oZCw3(YV~t5*)83koe{1R$h5Zxy}r+mPAf)GGAjw5m=>FK)~BEI-jC z$iz_~JFz9@r#M4X4wD6K1(7&Wxa9>+LR27|B_zu%-fTIB{S1mMmQI!CGVkUhvn8k{ zgkCK#rn)5BydvRH7?Tc&*^;y&ylDM6)CndFCeHf+xfsmSsos*rK=nv}%XU2yYas|? zUUJBc)JfP;ud<<5Z%sB!4_@CQHS%ML(pEd}EHMQOxC2^7{H8~{H{!E^Vd)Y(b4=A9 zCP6|G92c}a1v;!j(o~*A-H0laoU*NU03?Hb>*nSK%JwP~72=Mx57lbu%(#r#`PGPP z5&JoWzy6&&`?qXiPGiT5Rb)b&S9^4HXy?u)+BjY4pI_Q=J%?ED{QPu~#e1He0u$3U zu-C9;@_;t+_G#Gfos%dWEgZb9orVA;P^A1Qj;zf40sKplSEY)Jp zZnZsU$SSRZU`XOsHE*&@J0vo#2tK@9#U zl$rLiVaWnZaKV|~r~u+|NXop3S*HXZja;aZUG2G4+Yu9@lSgWRD0NsSPAbm$*3hln z9kUG>uaTVE=72%~0<#S(pT=*Hv?DVLq&Tx?RHT&|4Zyi^h<$50G4(L;ma}TSoW+7) zXC=%A{%d6#P*q-5Kll`4A{BH3Mmw57^oBI@*m%Zi#%hj-Ufh})YY1XVT#=Y_>SZA! zuu{mseo(e35UHrLs~)eBbYEUKZArz9wc0@#+u%*2e~kGwn9IzOHxCuDa#EK9XcSmO z0#4MifJ|_78kMKPoScV(oDZy~!wjpdX*0wqAW`({*tIF7fk3dcRUk1D#+Z!=OTbL4 zVg(>gr;>SWiy4C^PT1LQquq9z7SO-lZreV#+-y%gq>@%UN>@?PgNW(VOC+X4=(LGU z(5-EeQV2W=SFUIeq-1qlA=+ns5n1A-7)H-c$9F=OdYlxDZ%=&3uvQ7mv8R{f@H>FsF8lrOhUo6U8^~5$_%DmQfS9<(xHigClWzu zb{R8QA`4-+9BLj z-R3zpYXyt63UO3-;+*ab>W+tMGdR|~GBp%az1xDO$FiNJ6V(=Ltxj4BD+$bub_PD3 z`kft1^!Lc@!e-{0+O7cqEH+ajWqr^JkOc*u)w4ht6ApF**?vFRyc4>$f^1DK!)~;Z zTAa@w$#B+b5`FIqW~g*VRIS}amG}ub0bY|&ZFj+x>9(X?KSRU0R)X@eEVtn_+q|k^iRI+WaJ!L~aVk^9PNSTL`-EG>}f-ePMFm}4h0<2g5@_lRZ( ztTh5NK1JK7`fo7-jtS$6_%E<~@ZiCIeGaA?2h2&>`^^5B#Z`SI0*`zvdnXj9N1)T+$lkOhUigoHw9(0(9AFJek-1J zYdBp2G)uNwXTXgb9q}r5+75d-ame*hcsc64f(S3;DAjBn7NBQXG+^azZUDuyHSTVNMYZ%1KP^ zEL4f*S48xL6IOX?P6!gQMwr8{cUm|0UOO<*w|*7g(lX|*T?vaTnTe@jHgyd^*QI2n z&MWp5^%KQ0QWD=ip3tnGgjPG@!Z37kVC&Xq9A?s&Co)e;?qr$pf*E_7fy0|NK|Xf& z9I#Lk|LH>CYEdU-EzFK(-gEI2%P1P78q_-waxGX7=tBL&x#Nx`r`4MtV5r-H5F#44 z#iun9lcHet!oC0vtmtjobDrZj!|JcY!&nE z!f{wWG$4BpvRNH)s^W)=19V#{Hc!OGOa~Yht}w*83Qqpa&Abqcbjk~!dFFCYjJwIA zzPnsCxaFju(SZUga}CGinLe57#ED@3b@=oVuVa3eY6fTNOSR;4AH1%P!^Ij9B9CgT z*s)ujO}=+!UgR-NLf>Itp5agZtNoL7YNi|*79pK|Jxp?UQL5y9VGe;Kgxn5XpKq>G z-mxcfYtxfN0yA#i3C^(sCJP6bU_1l@825+8r;@VV)mFRPBB<_}}fEk4rG#nV{ z;zc6TPDsk99sHMdyl|aDCVFjEB|FITD#fCrve%uRU!E26Q~OEo5Z#8AT_aMHR0E98 zy$Q{0>1H7LnlSd#3aACD)pD&C*u|8*>ZzggPA8N-sCH^_>dh5VJDuGs4Ej!LBnx9D z!d`IG!5!<>W&s&Rg|RfFTCG#>R=!zN(#G0Tep=>Jd>N`{%}eU(4Y=rk7CXtUg4fuY zp$%RYIpk!?=w5Vp>~D#2Ojp{WOV%kyaInyzk(nfg`stR?g#u9%YOPqJhM!)UEn4g> za_6|PBD=a>Up=DYuPSFIx#&$00(_yo$TZH{yj*Nsw(9I6r@3U_a_+7JwBaoYBhofV zVDQh7nG851w8hH`>nv5mCSlW1S>aNpH=Yfwq}}dzz4aOJC$n!l#3V^E+viXF!7Lzq z&E^RmiIuvVF~ZwKJX9zA!-`R-m-h_ z6tjH5HkOLlQ~P<&qmw(kFVd}vHjLPYTT6Wr75BrA8Oc`jyuD=BF-cshN=5PLkAoWN7E_O3$)yWz$$TRbI6J4 zDO+Qu7W-Qb^;-`zHkP~W*;7x-UV8alF{ut%u+P{dD@x#5HAEDa!s5J{0qT+qJg$IE zJI9bX?ARD4g*$b4rwrHzdD>xinWbKBb#eoDn>E8OhIx>_K({4~M86`v&)Wl~9Pvpj zG2=vq9T5-Ntv|OFnm@F_bKtT+6xJ5ki8vU ziOBH%?lCC6u=?{W+}tBf6v-~sx>GZm%bjCcG=zeYX9}Xobnk@8E-Z9b;x3Us8cx+T z+z_0bTM|CBz|(7&{4Fw>p2*6MU1OC-93=(w!te!Wii~0+GJJIsd{eBFjwP15+mZqO+%T?F);CCiHFBG(>C;Oi^WUvJu)6idl0Pa%PX&y zoJ*Kf=u!oJ@0b>34^E;Fk^DJgucOE&^Piq zn4lJ$%;A%Blfr96?-5u|Bx`7cy?`9pdn93rlCR3pm4n&a>g?_6?CqND?b__^y6kOD z_O{mG(7WWD%U;-vo~PmQE&oHtr{3{d|Ic=j}ycAM_oh7E7PWm1hmR0nF zS2~O=wi3$$lji~N76A*X*q+_>+ENi_JZS>p< zxZ{n>c8={F$=5Mh8CX_y0SvJyTm=NX{yJ7cAY5)3o7mmIcI~y-_YdZkX)+Bb>>u&L z9zjJz*4nBYFFV-3O>TB+mN#v(A>uH|;9%vNW#e8F+Ft)w7|-DPmfz-dbi?X(mb~83 z>swdcU=8-ITedI^cbpiKC6Tz0XQg7~QmG8_6$X*hOkB$=IlkxF52LmR2I0)uRu!c- zPVX|plwA)2YK6kFtZNc(Xuy8)%!opVckhi_%QRF(~g zq_(3z=-%_(ka{Ujtwiy%b@0c8wDFn1v)ZS?rD!sq1@);`S+=_hLY#3e@>SYiGw~y< zUb0!xNAs2mn+XbEgvn85RB&m*GY(Apc$s#n113<>L+lI96G-qow#U9kisjY#+T~Vn z+YUW*mNpuD7jT7ku@V8g~=p#NOr$}#&=u1d2Q0WD0~2Xd2@DvScD8$ zDa>~yqs9#sLke3SOsP!EIAaCXkTH{m})fFCubVhC_af_zU|z2LERcBckxiuWxjkjP&Ymng!aWcAkUb|1(8 zMiiLGfg5Dq*NT{(Q`YifIUISzDVz8PhH^X? zdtk@QEeYDo!t){~J!-+hz|t-Y2d3#$#F))|Y{=}!QH1>8%$HuxB&Hyfs5C{KxJd!t zRY)k1Vo3Dm@cM>pG3ZBQjHjSzZ?%qh=}(Q8}1bb4V$+I^a) z+j*yuvx;8%jiP@gq}OSI{LHS$TTU3HSIr=EUVfOq2=2#-fikwek{kk&D74^^=rm>Q z#XG13Yoa6+CZT66R3d-PjshWw^4iA@Rzzg-{anFl!ip5zlSn5YFFtsRM0Nu6>2b2CWH$>m zUa}2p4?JN#KKWsHDV74pSbww_pIl&P-IWPHHDOB&C|;K8q@B=Pp_h?4XUUM|;Yg^k z#2tf*ND81islDXcfzla5l(}t}bXkv1h3yq0m-%KMyc8nYb1BZS&R%Si8~q;RzWeMC znY~FYDy-}v2OILXuL${z4Q16hjkJ4pdxIHG6LF@M7kK&xtB9qv!UAe4APbs146M*; z?*-rABZ7f{-sx(vW51@#U0% zssw^yt%s+|6lzdCU7pvT!%3TjBzYyoM{J6;Sng>^zTpqQl2 zqkJC1qI~h~8EDjGqNO%j8rf`%5h0S9Qj zIYw5SqMmESKAHu!P*LN`wR`_mpwq5c^B0d@c|58m)Fm1}N^)|cpj7TpDbf(>*v62V zWwlaUkHQWpXHm^RTOX(9+*Y9r@TrIK<2KWvXmYUzzFYO!B~E*4vkF7|otiQgqk3y< z=88j*u+R2d2(IFJu-nZJ(u18DS~W}~@-`KjAu%ZqliqB%!CzZ#WwJ3xNkiw0#MbGj z>wYVy3j;DGyH%G=iJ+bLlhz|(P4vkdy3E%?G13W`TOmLi+GrXeolv4UG4KkviK*>N zjm7TZXQvn=eGYZ3G(bA5%czERQ?Nd1`k7r21u?2&rc_zhTIVR~t}MFu$d_+>4ZHeC zjMXcN?QYkypy<#U5-`s+i0RHb%vp$-M=W8;KI%%5EgzquNWIvu5;rf%t1k3~T*JY> z+01MoLAzpwA@fx)CX;pzw#bQ0#z?^gSfi=AXRNjcP3JZPlt}N9M&J{x68$48qF)?e1JUr$HGx! zI)wEg;^PUrqdQcJ#b*8hc48k(*_vD6ac7;%iVWrC%W2p%SbBGrOwkz|&-CT9t*y(b zlopJAKRs+5Z$*IyX{GAo=gCi~yFoxU640H}DNetbt9e8>J7N}D#JP1BrPg}h zVf2`v_IMUVE~2-q=wO$dIO4!PpiH`iG0xz~?}dG^!d~pCPC@Ie)TrIVG34y!Vr8qu z!lDMzVHJIH^JZHoGo}G1CQ5=Y_xBJH>A0$HQD<~XN`5R=T|x)CUfV6FDi-S!?Dk{* z%7LcewBr-77Wi9OcB;`vH-b25hIYfXCAZs&OTGO}LFQ4CDuS7|4DsYKN<_6778QfT z21wbbpmSDJ#hWizh7^6yQRgId3$iS85*B3I-URjl#PHt7$xezQyUL%c!UEAntvG!qMM60?0T4vn{A5TV8dS6PwMYLhFLIlD#I&$2^&LhWpV?4zA^s+K*IxRtIB8V*HN zFfM)CdWS1tVRn?#tFJ}x%KQR$6JoZ$~#IFlF(N1 zR|s3#VRyyDPf;)+1XbvZNdN<-2A+h2*%cJOAR4|re*>VerCsweu<0(3Totrq6bWC9 zj2E%JSbbu4&`;u~v#{IKd{L?w(lLF8&tj-&6^mKcU0qw!FX!v&14@`%JdGEj1JFH zR}!PU=JFV-7Fg(d{yo}^_<_Vn*rjF^L(P|JCZSf^F!57QA7P)S62@oi!yzqHU8{x0 z-F}z+vZELaua|lMrM)kT03t8GMV8H>XnU)dxQ1N)^f9%iwQ|e2*!*v$)E@ zFax>+4MKJ|bWt-$N#qgrr;9=OPqM8{DQ{k!)s*_g>D<(*7ZE^QMzNRK8t6JzU(-{G ze&aL_-=%M4)zwe`{ecAJJFN7gp3)8Ld^mBj%bbHGvUIuFrQ}GI#S3I!L+n+;0vd#O zJb8q_pHQ2CB4It*Sc(JjVsai2YePOp>EU#EF0#r;)5E3V`t(5&i`Et&cIeZX62K-avu(tw=fN=HABEcIhKDBv$cpmX~ishf*Syxxox85uDuIkG=rYn>! z?y(?=%qZQ4)6b9@h9;!SCvjs}9duhDkC>aT^i@17#F z0&{oh;-4%ow_9+!9>w>JEa4g^B{l34wXC&*JzHsDe`SF)JNNpCr*R*%Cj~TRD+58|C}Yn0)daVB??Fvr6B*V83I# zZkrtt+~`ubK)fT}ZgIL>DV^=Qk9Lv%+dh`{6fXh8eMc#04k2CEWkQ=M%&E~W-zFNA z%&0P5_&pMdc2EC_pP6v+ugp<&H8C7$zr+^smoTK++8Wf9GAsTXLX_LH zDZV45#9)d}&OFHj%=A|$LbgN?n90$`V~A%N8ZI@|Bdcg*;kDo@D3aSEp}DRLo&5U8 z;t+W9vs5fOy{JbXAfK+Lqf=q3?YMeH<)v;D)<+k=nH6#6=#&cHR~9QXu6z^V`0N*| z@&Cb<)g8*ZUEmcuz1ZggX>E4WXNG{>rPS?W&)F=>?B}HqXiM}J1aFaEM-Q1+dU*{J zrdTM}B1@wYrIz~VGwy14n{|~O zn;O4RK(FS|cM1Gi0$uEr@zj3U_5@KJyfwvN5$LMjwgD}-ALlO<9CrSxk%<&mm^$H7 zr<7?r`6mjwwu9C3Hwv5;=^!kQ!*3Jl*sE;%4<*_edAE{Dhf9( zTzYBjk7uWMUWFWv_;#{cd31E-69`kp`(RgUbyw=@uGBSMscXAZ*L9`VbfvN{BIvO2 zt4q>Xw+j%u|$%xxW1f+1rEGk$+ccp;E3~$j Y?b{0Jh(S-pL!W!l2Nrbc?S$t40V@), 2005. +# Ricardo Javier Cardenes Medina , 2005. +# Marc Fargas , 2007. +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 11:05+1100\n" +"PO-Revision-Date: 2007-01-19 10:23+0100\n" +"Last-Translator: Marc Fargas \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "Ja existeix %(object)s amb aquest %(fieldname)s." + +#: db/models/manipulators.py:306 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "i" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Si us plau, introdueixi un %s vàlid." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Separi múltiples IDs amb comes." + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Premi \"Control\" o \"Command\" en un Mac per escollir més d'un." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Si us plau, introdueixi IDs de %(self)s vàlids. El valor %(value)r és " +"invàlid." +msgstr[1] "" +"Si us plau, introdueixi IDs de %(self)s vàlids. Els valors %(value)r són " +"invàlids." + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s." + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: oldforms/__init__.py:352 newforms/fields.py:78 newforms/fields.py:373 +#: newforms/fields.py:449 newforms/fields.py:460 +msgid "This field is required." +msgstr "Aquest camp és obligatori." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Aquest valor ha de ser un enter." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Aquest valor ha de ser True (Veritat) o False (Fals)" + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Aquest camp no pot ser null (estar buit)." + +#: db/models/fields/__init__.py:454 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD." + +#: db/models/fields/__init__.py:521 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM." + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengalí" + +#: conf/global_settings.py:41 +#, fuzzy +msgid "Catalan" +msgstr "Italià" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "Chec" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "Galès" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "Danès" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "Alemany" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "Grec" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "Anglès" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "Espanyol" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "" + +#: conf/global_settings.py:50 +#, fuzzy +msgid "Finnish" +msgstr "Danès" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "Francés" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "Galleg" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "Húngar" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "Hebreu" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "Islandés" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "Italià" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "Japonés" + +#: conf/global_settings.py:58 +msgid "Latvian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Macedonian" +msgstr "" + +#: conf/global_settings.py:60 +msgid "Dutch" +msgstr "Holandés" + +#: conf/global_settings.py:61 +msgid "Norwegian" +msgstr "Norueg" + +#: conf/global_settings.py:62 +#, fuzzy +msgid "Polish" +msgstr "Anglès" + +#: conf/global_settings.py:63 +msgid "Brazilian" +msgstr "Brasileny" + +#: conf/global_settings.py:64 +msgid "Romanian" +msgstr "Rumanés" + +#: conf/global_settings.py:65 +msgid "Russian" +msgstr "Rús" + +#: conf/global_settings.py:66 +msgid "Slovak" +msgstr "Eslovac" + +#: conf/global_settings.py:67 +msgid "Slovenian" +msgstr "Esloveni" + +#: conf/global_settings.py:68 +msgid "Serbian" +msgstr "Serbi" + +#: conf/global_settings.py:69 +msgid "Swedish" +msgstr "Suec" + +#: conf/global_settings.py:70 +msgid "Tamil" +msgstr "" + +#: conf/global_settings.py:71 +msgid "Turkish" +msgstr "" + +#: conf/global_settings.py:72 +msgid "Ukrainian" +msgstr "Ucranià" + +#: conf/global_settings.py:73 +msgid "Simplified Chinese" +msgstr "Xinés simplificat" + +#: conf/global_settings.py:74 +msgid "Traditional Chinese" +msgstr "Xinés tradicional" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "any" +msgstr[1] "anys" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mes" +msgstr[1] "mesos" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "setmana" +msgstr[1] "setmanes" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dia" +msgstr[1] "dies" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "hores" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minut" +msgstr[1] "minuts" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Dilluns" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Dimarts" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Dimecres" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Dijous" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Divendres" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Dissabte" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Diumenge" + +#: utils/dates.py:14 +msgid "January" +msgstr "Gener" + +#: utils/dates.py:14 +msgid "February" +msgstr "Febrer" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Març" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maig" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Juny" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Juliol" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agost" + +#: utils/dates.py:15 +msgid "September" +msgstr "Setembre" + +#: utils/dates.py:15 +msgid "October" +msgstr "Octubre" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembre" + +#: utils/dates.py:16 +msgid "December" +msgstr "Desembre" + +#: utils/dates.py:19 +msgid "jan" +msgstr "gen" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "oct" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "des" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Gen." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Oct." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Des." + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "F j, Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "F j, Y, H:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/translation/trans_real.py:380 +#, fuzzy +msgid "YEAR_MONTH_FORMAT" +msgstr "F j, Y" + +#: utils/translation/trans_real.py:381 +#, fuzzy +msgid "MONTH_DAY_FORMAT" +msgstr "F j, Y" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Aseguris de que el seu texte té menys de %s caracter." +msgstr[1] "Aseguris de que el seu texte té menys de %s caracters." + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "No es permeten salts de linea." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s." + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:150 +#: newforms/widgets.py:162 +msgid "Unknown" +msgstr "Desconegut" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "Yes" +msgstr "Si" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "No" +msgstr "No" + +#: oldforms/__init__.py:667 core/validators.py:173 core/validators.py:442 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "El fitxer enviat està buit." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Introdueixi un número enter entre -32,768 i 32,767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Introdueixi un número positiu." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Introdueixi un número entre 0 i 32,767." + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "clau de la sessió" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "dades de la sessió" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "data de caducitat" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sessió" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sessions" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "" + +#: contrib/auth/forms.py:25 +#, fuzzy +msgid "A user with that username already exists." +msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"El seu navegador no sembla tenir les 'cookies' (galetes) activades. Aquestes " +"són necessàries per iniciar la sessió." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Si us plau, introdueixi un nom d'usuari i contrasenya vàlids. Tingui en " +"compte que tots dos camps son sensibles a majúscules i minúscules." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "" + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "" + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "" + +#: contrib/auth/views.py:39 +#, fuzzy +msgid "Logged out" +msgstr "Finalitzar sessió" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nom" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nom en clau" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permís" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permissos" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "grup" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "grups" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "nom d'usuari" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "nom propi" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "cognoms" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "adreça de correu electrònic" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "contrasenya" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "és membre del personal" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Indica si l'usuari pot entrar en el lloc administratiu." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "actiu" + +#: contrib/auth/models.py:96 +#, fuzzy +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "Indica si l'usuari pot entrar en el lloc administratiu." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "estat de superusuari" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "últim inici de sessió" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "data de creació" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els " +"permissos dels grups dels que sigui membre." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "permissos de l'usuari" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "usuari" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "usuaris" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Informaciò personal" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "permissos" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Dates importants" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Grups" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "missatge" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "nom de la classe del model en python" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "tipus de contingut" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "tipus de continguts" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirigir desde" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Aquesta ruta hauria de ser el camí absolut, excluint el nom del domini. " +"Exemple '/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirigir a" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Això pot ser bé una ruta absoluta (com abans) o una URL completa que comenci " +"per http:// ." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redirecció" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redireccions" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:315 +msgid "URL" +msgstr "URL" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Exemple: '/about/contact/'. Asseguri's de posar les barres al principi i al " +"final." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "tìtol" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contingut" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "habilitar comentaris" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nom de la plantilla" + +#: contrib/flatpages/models.py:13 +#, fuzzy +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Exemple: 'flatpages/contact_page'. Si no el proporciona, el sistema " +"utilitzarà 'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "ha de estar registrat" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Si està marcat, només els usuaris registrats podran veure la pàgina." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "pàgina estàtica" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "pàgines estàtiques" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID de l'objete" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "encapçalament" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentari" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "calificació 1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "calificació 2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "calificació 3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "calificació 4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "calificació 5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "calificació 6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "calificació 7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "calificació 8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "es calificació vàlida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/hora d'enviament" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "és públic" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "Adreça IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "està eliminat" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Marqui aquesta caixa si el comentari és inapropiat. En lloc seu es mostrarà " +"\"Aquest comentari ha estat eliminat\" " + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "comentaris" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Objete Contingut" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Enviat per %(user)s el %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nom de la persona" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "adreça ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprovat per el \"staff\"" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "comentari lliure" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "comentaris lliures" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "puntuació" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data de la puntuació" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "puntuació de karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "punts de karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d punt per %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Aquest comentari va ser marcat per %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "data de la marca" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "marca d'usuari" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "marques d'usuari" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Marca de %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data d'eliminació" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "eliminació del moderador" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "eliminacions del moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "eliminació del moderador per %r" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Usuari:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "Finalitzar sessió" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Contrasenya:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Contrasenya oblidada?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Calificacions" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requerit" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Enviar una fotografia" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Comentari:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Previsualitzar comentari" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "El seu nom:" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Els usuaris anònims no poden votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID del comentari invàlid" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "No pots votar-te a tu mateix" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Es precisa aquesta puntuació perquè has introduit almenys un altre." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s " +"comentari:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s " +"comentaris:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Aquest comentari va ser publicat per un usuari incomplert\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Només s'admed POST" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Un o més dels caps requerits no ha estat sotmés" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" +"Algú està jugant amb el formulari de comentaris (violació de seguretat)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"El formulari de comentaris tenia un paràmetre 'target' invàlid -- el ID del " +"objecte era invàlid" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" +"El formulari del comentari no ha proveit ni 'previsualitzar' ni 'enviar'" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nom del domini" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nom per mostrar" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "lloc" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "llocs" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

        By %s:

        \n" +"
          \n" +msgstr "" +"

          Per %s:

          \n" +"
            \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Tots" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Cualsevol data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Avui" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últims 7 dies" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Aquest mes" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Aquest any" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "moment de l'acció" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id del objecte" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "'repr' de l'objecte" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "marca de l'acció" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "missatge del canvi" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada del registre" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entrades del registre" + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "Totes les dates" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +msgid "Home" +msgstr "Inici" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Documentació" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "'Bookmarklets'" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Canviar clau" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "'Bookmarklets' de documentació" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

            To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

            \n" +msgstr "" +"\n" +"

            Per a instalar 'bookmarklets', arrosegui l'enllaç a la " +"seva barra de\n" +"marcadors, o faci click amb el botò dret en l'enllaç i afegeixi'l als " +"marcadors.\n" +"Ara pot escollir el 'bookmarklet' desde cualsevol pàgina del lloc.\n" +"Observi que alguns d'aquests 'bookmarklets' precisen que estigui veient\n" +"el lloc desde un ordinador senyalat com a \"intern\" (parli\n" +"amb el seu administrador de sistemes si no està segur de la condició del " +"seu).

            \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentació d'aquesta pàgina" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"El porta desde cualsevol pàgina de la documentació a la vista que la genera." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostra el ID de l'objecte" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Mostra el 'content-type' (tipus de contingut) i el ID inequívoc de les " +"pàgines que representen un únic objecte." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar aquest objecte (finestra actual)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"El porta a la pàgina d'administració de pàgines que representen un únic " +"objecte." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar aquest objecte (nova finestra)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Com abans, però obre la pàgina d'administració en una nova finestra." + +#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:9 +msgid "Delete" +msgstr "Eliminar" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Desar com a nou" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Desar i afegir-ne un de nou" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Desar i continuar editant" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Desar" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Error del servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Error del servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Error del servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Hi ha hagut un error. S'ha informat als administradors del lloc per correu " +"electrònic y hauria d'arreglar-se en breu. Gràcies per la seva paciència." + +#: contrib/admin/templates/admin/filter.html:2 +#, fuzzy, python-format +msgid " By %(filter_title)s " +msgstr "Per %(title)s " + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Cercar" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Històric" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Usuari" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Acció" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "F j, Y, H:i " + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Aquest objecte no te historial de canvis. Probablement no va ser afegit " +"utilitzant aquest lloc administratiu." + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, fuzzy, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Eliminar el/la %(object_name)s '%(object)s' provocaria l'eliminació " +"d'objectes relacionats, però el seu compte no te permisos per a esborrar els " +"tipus d'objecte següents:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, fuzzy, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Està segur voler esborrar els/les %(object_name)s \"%(object)s\"? " +"S'esborraran els següents elements relacionats:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Si, estic segur" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Afegir %(name)s" + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Afegir" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Veure en el lloc" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Si us plau, corregeixi l'error mostrat abaix." +msgstr[1] "Si us plau, corregeixi els errors mostrats abaix." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Ordre" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Ordre:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Benvingut," + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "No s'ha pogut trobar la pàgina" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Ho sentim, però no s'ha pogut trobar la pàgina solicitada" + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "Iniciar sessió" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Models disponibles en la aplicació %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, fuzzy, python-format +msgid "%(name)s" +msgstr "Afegir %(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "No té permís per editar res." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Accions recents" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Les meves accions" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Cap disponible" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Lloc administratiu de Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Adminsitració de Django" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +#, fuzzy +msgid "Username" +msgstr "Usuari:" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +#, fuzzy +msgid "Password" +msgstr "Contrasenya:" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#, fuzzy +msgid "Password (again)" +msgstr "Canvi de clau" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Actualment:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora:" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Gràcies per emprar algun temps de cualitat amb el lloc web avui." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Iniciar sessió de nou" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Està rebent aquest missatge degut a que va solicitar un restabliment de " +"contrasenya." + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "del seu compte d'usuari a %(site_name)s." + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "La seva nova contrasenya és: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Sentis lliure de canviar-la en aquesta pàgina:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "El seu nom d'usuari, en cas d'haver-lo oblidat:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Gràcies per fer us del nostre lloc!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "L'equip de %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "Restablir contrasenya" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Contrasenya restaber-ta amb èxit" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Li hem enviat una contrasenya nova a l'adreça de correu electrònic que ens " +"ha indicat. L'hauria de rebre en breu." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Canvi de clau" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Si us plau, introdueixi la seva contrasenya antiga, per seguretat, i tot " +"seguit introdueixi la seva nova contrasenya dues vegades per verificar que " +"l'ha escrit correctament." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Contrasenya antiga:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Contrasenya nova:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirmar contrasenya:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Canviar la meva clau:" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Canvi de clau exitò" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "La seva clau ha estat canviada." + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Ha oblidat la seva contrasenya? Introdueixi la seva adreça de correu " +"electrònic i crearem una nova que li enviarem per correu." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Adreça de correu electrònic:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Restablir la meva contrasenya" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Lloc administratiu" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "Pot editar-lo de nou abaix." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Pot agregar un altre %s abaix." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Agregar %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Agregat %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Modificat %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Eliminat %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Cap camp canviat." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Un o més %(fieldname)s en %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Està segur?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Modificar històric: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Seleccioni %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Seleccioni %s per modificar" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Si us plau, identifiquis de nou doncs la seva sessió ha expirat. No es " +"preocupi, el seu enviament està emmagatzemat." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Sembla ser que el seu navegador no està configurat per acceptar " +"'cookies' (galetes). Si us plau, habiliti les 'cookies', recarregui aquesta " +"pàgina i provi-ho de nou. " + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Els noms d'usuari no poden contenir el caracter '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"La seva adreça de correu no és el seu nom d'usuari. Provi '%s' en tot cas." + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "" + +#: contrib/admin/views/doc.py:164 +#, fuzzy, python-format +msgid "App %r not found" +msgstr "No s'ha pogut trobar la pàgina" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Enter" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Booleà (Verdader o Fals)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Cadena (fins a %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Enters separats per comes" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Data (sense hora)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Data (amb hora)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Adreça de correu electrònic" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Ruta del fitxer" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Número decimal" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Booleà (Verdader, Fals o 'None' (cap))" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relació amb el model pare" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Número de telèfon" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Texte" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Estat dels E.U.A. (dos lletres majúscules)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "Texte XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "" + +#: contrib/admin/views/auth.py:30 +#, fuzzy +msgid "Add user" +msgstr "Agregar %s" + +#: contrib/admin/views/auth.py:57 +#, fuzzy +msgid "Password changed successfully." +msgstr "Canvi de clau exitò" + +#: contrib/admin/views/auth.py:64 +#, fuzzy, python-format +msgid "Change password: %s" +msgstr "Canviar clau" + +#: newforms/fields.py:101 newforms/fields.py:254 +#, fuzzy, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Aseguris de que el seu texte té menys de %s caracter." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, fuzzy, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Aseguris de que el seu texte té menys de %s caracter." + +#: newforms/fields.py:126 core/validators.py:120 +msgid "Enter a whole number." +msgstr "Introdueixi un número senser." + +#: newforms/fields.py:128 +#, fuzzy, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Aquest valor ha de ser una potència de %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "" + +#: newforms/fields.py:163 +#, fuzzy +msgid "Enter a valid date." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:190 +#, fuzzy +msgid "Enter a valid time." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:226 +#, fuzzy +msgid "Enter a valid date/time." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:240 +#, fuzzy +msgid "Enter a valid value." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:269 core/validators.py:161 +msgid "Enter a valid e-mail address." +msgstr "Introdueixi una adreça de correu vàlida." + +#: newforms/fields.py:287 newforms/fields.py:309 +#, fuzzy +msgid "Enter a valid URL." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:311 +#, fuzzy +msgid "This URL appears to be a broken link." +msgstr "La URL %sés un enllaç trencat." + +#: newforms/fields.py:359 +#, fuzzy +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s." + +#: newforms/fields.py:377 newforms/fields.py:453 +#, fuzzy +msgid "Enter a list of values." +msgstr "Introdueixi un nom de fitxer vàlid." + +#: newforms/fields.py:386 +#, fuzzy, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s." + +#: template/defaultfilters.py:436 +msgid "yes,no,maybe" +msgstr "si,no,potser" + +#: views/generic/create_update.py:43 +#, fuzzy, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s." + +#: views/generic/create_update.py:117 +#, fuzzy, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit." + +#: views/generic/create_update.py:184 +#, fuzzy, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "L'equip de %(site_name)s" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Aquest valor ha de contenir només números, guions, i guions baixos." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Aquest valor ha de contenir només lletres, números, guions, guions baixos, i " +"barres (/)." + +#: core/validators.py:72 +#, fuzzy +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"Aquest valor ha de contenir només lletres, números, guions, guions baixos, i " +"barres (/)." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "No es permeten majúscules aquí." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "No es permeten minúscules aquí." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Introdueixi només dígits separats per comes." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Introdueixi adreces de correu electrònic vàlides separades per comes." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Per favor introdueixi una adreça IP vàlida." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "No s'admeten valor buits." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "No s'admeten caracters no numèrics." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Aquest valor no pot contenir només dígits." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Només s'admeted caracters alfabètics aquí." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "" + +#: core/validators.py:143 +#, fuzzy, python-format +msgid "Invalid date: %s." +msgstr "URL invalida: %s" + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Introdueixi una hora vàlida en el format HH:MM." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envii una imatge vàilda. El fitxer que ha enviat no era una imatge o estaba " +"corrupte." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "La URL %s no apunta una imatge vàlida." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"El números de telèfon han de guardar-se en el format XXX-XXX-XXXX. \"%s\" no " +"és vàlid." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "La URL %s no apunta a un video QuickTime vàlid." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Es precisa d'una URL vàlida." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Es precisa HTML vàlid. Els errors específics sòn:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML incorrectament formatejat: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL invalida: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "La URL %sés un enllaç trencat." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Introdueixi una abreviatura vàlida d'estat d'els E.U.A.." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Vigili la seva boca! Aquí no admetem la paraula: %s." +msgstr[1] "Vigili la seva boca! Aquí no admetem les paraules: %s." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Aquest camp ha de concordar amb el camp '%s'." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Si us plau, introdueixi alguna cosa alemnys en un camp." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Si us plau, ompli els dos camps o deixi'ls tots dos en blanc." + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "S'ha de proporcionar aquest camps si %(field)s és %(value)s" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "S'ha de proporcionar aquest camps si %(field)s no és %(value)s" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "No s'admeten valors duplicats." + +#: core/validators.py:364 +#, fuzzy, python-format +msgid "This value must be between %s and %s." +msgstr "Aquest valor ha de ser una potència de %s." + +#: core/validators.py:366 +#, fuzzy, python-format +msgid "This value must be at least %s." +msgstr "Aquest valor ha de ser una potència de %s." + +#: core/validators.py:368 +#, fuzzy, python-format +msgid "This value must be no more than %s." +msgstr "Aquest valor ha de ser una potència de %s." + +#: core/validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "Aquest valor ha de ser una potència de %s." + +#: core/validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "Si us plau, introdueixi un número decimal vàlid." + +#: core/validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digit." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digits." + +#: core/validators.py:422 +#, fuzzy, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digit." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digits." + +#: core/validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digit " +"decimal." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid amb no més de %s digits " +"decimals." + +#: core/validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Asseguris de que el fitxer que ha enviat té, com a mínim, %s bytes." + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Asseguris de que el fitxer que ha enviat té, com a màxim %s bytes." + +#: core/validators.py:453 +msgid "The format for this field is wrong." +msgstr "El format per aquest camp és incorrecte." + +#: core/validators.py:468 +msgid "This field is invalid." +msgstr "El camp no és vàlid." + +#: core/validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "No s'ha pogut obtenir res de %s." + +#: core/validators.py:507 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"La URL %(url)s ha va tornar la capcelera Content-Type '%(contenttype)s', que " +"no és vàlida." + +#: core/validators.py:540 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Si us plau, tanqui l'etiqueta %(tag)s desde la linea %(line)s. (La linea " +"comença amb \"%(start)s\".)" + +#: core/validators.py:544 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Part del text que comença en la linea %(line)s no està permés en aquest " +"contexte. (La linea comença per \"%(start)s\".)" + +#: core/validators.py:549 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"El \"%(attr)s\" de la linea %(line)s no és un atribut vàlido. (La linea " +"comença per \"%(start)s\".)" + +#: core/validators.py:554 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"La \"<%(tag)s>\" de la linea %(line)s no és una etiqueta vàlida. (La línea " +"comença per \"%(start)s\".)" + +#: core/validators.py:558 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"A una etiqueta de la linea %(line)s li falta un o més atributs requerits.(La " +"linea comença per \"%(start)s\".)" + +#: core/validators.py:563 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"L'atribut \"%(attr)s\" de la linena %(line)s té un valor que no és vàlid. " +"(La linea comença per \"%(start)s\".)" + +#~ msgid "Have you forgotten your password?" +#~ msgstr "Ha oblidat la seva clau?" + +#~ msgid "Use '[algo]$[salt]$[hexdigest]'" +#~ msgstr "Utilitzi '[algo]$[salt]$[hexdigest]'" diff --git a/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..412c2eb87672d4c069c06d801794b83d2e9b4c60 GIT binary patch literal 1520 zcwSwT&yO256vxeO0yO+yen?0lSwa=9stK8eg>AZ2O*b2~+f8@132ou#%)FgAb?nIY z1Xk*u8@K)g2u?_d0}?{~1t9(dBm@^gaDiJTIPmp&)D^X4f1bzB?>)bK^V3@6F2i^M z^Ciq5Fki;J^AM}XU*N;w-{2~E4}2W_7km^vvl7qOz$d^L!4~)`cm`arxdk@C0r)Ps zzY_0%0yev&()@xBE)_#LF zXU+J&UuynZ^G`6eIM~m#(-D4$Cj0+*ip3g3zEDG`FXRe&AHhURs@g+c;jPbMK3m@n zZ+)z`p9#yGT(swH*2{C&TMDVfL=ivh*tSrlb75iVj1=3R8smry?&X~B?^Jc*NLN=B z2^+sDlig-}LYKmx@BvM1WekPQro6YXQo%mqd!;5U>V)^oqI8~*XyIu-AH!%l%#73kj^Zdk=mS+L@VOhPy6GQBX1_G4FD|Med4{ZDWhet>Nx)=kz6MyVKe>+LK27lLZR!^r3%k zp@h_z&|+a7`L}mR2g&B<#>FH(bq>WGlTCJXW^$>EF5jF;-@28&Lz--?^4e7~A%!Gq z+J2*TUzNvHNXyPT-j{A+w1hZMr73G|eyt$W9ha3#NnUEfr^bpXfDENWliqk2qQJ?( z37bB-2hWd4DdT6vf?lCGH10Rvn?$k7BB(V;R!O6HuP~KdgIX;GwU&WVtuR9PU_h;Y zCb*U!R*_4=dMIs2$+<*c*5~^W?Jd5?Z$PpGkx7mRa!1Q*tqXKq^5bw8l`B|ZmUGet q(nzIB9lC=b)>ov=QZQzw3|OL5@q=?>;t7B9QsVXVpIL8;*8T^Rl6Bz# literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..8903957 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/ca/LC_MESSAGES/djangojs.po @@ -0,0 +1,121 @@ +# translation of djangojs.po to +# Spanish translation for the django-admin JS files. +# Copyright (C) +# This file is distributed under the same license as the PACKAGE package. +# +# Jorge Gajon , 2005. +# Marc Fargas , 2007. +msgid "" +msgstr "" +"Project-Id-Version: djangojs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 11:05+1100\n" +"PO-Revision-Date: 2007-01-19 10:30+0100\n" +"Last-Translator: Marc Fargas \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s Disponibles" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Seleccionar tots" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Afegir" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Eliminar" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s Escollits" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Faci les seves seleccions i faci click a" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Deseleccionar tots" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Diumenge Dilluns Dimarts Dimecres Dijous Divendres Dissabte" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M X J V S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "Ara" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "Rellotje" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "Esculli una hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "Mitja nit" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "6 a.m." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "Migdia" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "Cancel·lar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "Avui" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "Calendari" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "Ahir" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "Demà" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "" diff --git a/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c2f04edb81d3d79459d1f7d6b2dcfb8012caf7f0 GIT binary patch literal 37754 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.po new file mode 100644 index 0000000..0dcc313 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/django.po @@ -0,0 +1,2110 @@ +# Translation of django.po to Czech +# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the DJANGO package. +# Radek Svarz , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: Django Czech translation\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:11+0200\n" +"PO-Revision-Date: 2006-10-07 13:10+0100\n" +"Last-Translator: \n" +"Language-Team: Czech\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Poedit-Country: CZECH REPUBLIC\n" + +#: contrib/comments/models.py:67 +#: contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID objektu" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "titulek" + +#: contrib/comments/models.py:69 +#: contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "komentář" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "hodnocení #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "hodnocení #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "hodnocení #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "hodnocení #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "hodnocení #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "hodnocení #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "hodnocení #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "hodnocení #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "je platné hodnocení" + +#: contrib/comments/models.py:83 +#: contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "datum/čas byl zaslán" + +#: contrib/comments/models.py:84 +#: contrib/comments/models.py:170 +msgid "is public" +msgstr "je veřejné" + +#: contrib/comments/models.py:85 +#: contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "IP adresa" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "je odstraněno" + +#: contrib/comments/models.py:86 +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "Zaškrtněte tento box, pokud komentář není vhodný. Místo něj bude zobrazena zpráva \"Tento komentář byl smazán\"." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "komentáře" + +#: contrib/comments/models.py:131 +#: contrib/comments/models.py:207 +msgid "Content object" +msgstr "objekt obsahu" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Zadané %(user)s dne %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "jméno osoby" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP adresa" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "scháleno správci" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "volný komentář" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "volné komentáře" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "body" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "expirace hodnocení" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "bod karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karma body" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d hodnoceno od %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Tento komentář byl označkován uživatelem %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "datum označení" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "uživatelské označení" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "uživatelská označení" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Označeno %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "datum smazání" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "vymazáno moderátorem" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "vymazané moderátorem" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Vymazáno moderátorem od %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonymní uživatelé nemohou hlasovat" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Neplatné ID komentáře" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Nelze hlasovat pro sebe" + +#: contrib/comments/views/comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "Toto hodnocení je povinné, protože jste zadal(a) alespoň jedno jiné hodnocení." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Tento komentář byl odevzdán uživatelem, který(á) odevzdal(a) méně než %(count)s komentář:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Tento komentář byl odevzdán uživatelem, který(á) odevzdal(a) méně než %(count)s komentáře:\n" +"\n" +"%(text)s" +msgstr[2] "" +"Tento komentář byl odevzdán uživatelem, který(á) odevzdal(a) méně než %(count)s komentářů:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Tento komentář byl odevzdán povrchním uživatelem:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Je povolená pouze metoda POST" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Jedno nebo více povinných polí nebylo vyplněné" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Někdo falšoval formulář komentáře (bezpečnostní narušení)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgstr "Formulář komentáře měl neplatný parametr 'target' -- ID objektu nebylo platné" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Formulář komentáře neobsahoval buď 'preview' nebo 'post'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Uživatelské jméno:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Odhlásit se" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Heslo:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Zapomenuté heslo?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Hodnocení" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Povinné" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Volitelné" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Zařadit fotografii" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Komentář:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Náhled komentáře" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Vaše jméno:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

            By %s:

            \n" +"
              \n" +msgstr "" +"

              %s:

              \n" +"
                \n" + +#: contrib/admin/filterspecs.py:70 +#: contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +#: contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Vše" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Libovolné datum" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Dnes" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Posledních 7 dní" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Tento měsíc" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Tento rok" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ano" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Ne" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Neznámé" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "čas akce" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "object id" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "object repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "příznak akce" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "zpráva změny" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "log záznam" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "log záznamy" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "Všechna data" + +#: contrib/admin/views/decorators.py:10 +#: contrib/auth/forms.py:59 +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +msgstr "Prosíme, vložte správné uživatelské jméno a heslo. Poznámka - u obou položek se rozlišuje velikost písmen." + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Přihlášení" + +#: contrib/admin/views/decorators.py:62 +msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." +msgstr "Prosíme, znovu se přihlašte, Vaše sezení vypršelo. Nemusíte se obávat, Vaše podání je uloženo." + +#: contrib/admin/views/decorators.py:69 +msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." +msgstr "Vypadá to, že Váš prohlížeč není nastaven, aby akceptoval cookies. Prosíme, zapněte cookies, obnovte tuto stránku a zkuste znovu." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Uživatelská jména nemohou obsahovat znak '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Vaše e-mailová adresa není Vaše uživatelské jméno. Zkuste místo toho '%s'." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Django správa" + +#: contrib/admin/views/main.py:257 +#: contrib/admin/views/auth.py:17 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "Záznam %(name)s \"%(obj)s\" byl úspěšně přidán." + +#: contrib/admin/views/main.py:261 +#: contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:22 +msgid "You may edit it again below." +msgstr "Můžete to opět upravit níže." + +#: contrib/admin/views/main.py:271 +#: contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Můžete přidat další %s níže." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "%s: přidat" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Záznam %s byl přidán." + +#: contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 +msgid "and" +msgstr "a" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "%s: změněno" + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Záznam %s byl smazán." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Nebyly změněny žádné pole." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" byl úspěšně změněn." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "The %(name)s \"%(obj)s\" byl úspěšně přidán. Můžete to opět upravit níže." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "%s: změnit" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Jedno nebo více %(fieldname)s z %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Jedno nebo více %(fieldname)s z %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "Záznam %(name)s \"%(obj)s\" byl úspěšně smazán." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Jste si jist(á)?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Historie změn: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Vybrat %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Vyberte %s pro změnu" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Databázová chyba" + +#: contrib/admin/views/doc.py:46 +#: contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "tag:" + +#: contrib/admin/views/doc.py:77 +#: contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filtr:" + +#: contrib/admin/views/doc.py:135 +#: contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "pohled (view):" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "Aplikace %r nenalezena" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Model %r v aplikaci %r nenalezen" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "související objekt `%s.%s`" + +#: contrib/admin/views/doc.py:183 +#: contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 +#: contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "model:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "související objekty `%s.%s`" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "%s: vše" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "%s: počet" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Pole na objektech %s" + +#: contrib/admin/views/doc.py:291 +#: contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 +#: contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 +#: contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Celé číslo" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Boolean (buď Ano (True), nebo Ne (False))" + +#: contrib/admin/views/doc.py:293 +#: contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Text (maximálně %(maxlength)s znaků)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Celá čísla oddělená čárkou" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Datum (bez času)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Datum (s časem)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "E-mailová adresa" + +#: contrib/admin/views/doc.py:298 +#: contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Cesta k souboru" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Desetiné číslo" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Boolean (buď Ano (True), Ne (False), nebo Nic (None))" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "V relaci k rodičovskému modelu" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Telefonní číslo" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Text" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Čas" + +#: contrib/admin/views/doc.py:315 +#: contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Stát US (2 velké znaky)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "text XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s pravděpodobně není objekt urlpattern" + +#: contrib/admin/views/auth.py:28 +msgid "Add user" +msgstr "Přidat uživatele" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dokumentace" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Změnit heslo" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Domů" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Historie" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Datum/čas" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Uživatel" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Akce" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. N Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "Tento objekt nemá historii změn. Pravděpodobně nebyl přidán přes administrátorské rozhraní." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django správa webu" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django správa" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Chyba serveru" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Chyba serveru (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Chyba serveru (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." +msgstr "Nastala chyba. Ta byla oznámena administrátorovi serveru pomocí e-mailu a měla by být brzy odstraněna. Děkujeme za trpělivost." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Stránka nenalezena" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Je nám líto, ale vyžádaná stránka nebyla nalezena." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Dostupné modely v aplikaci %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Přidat" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Změnit" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Nemáte oprávnění nic měnit." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Poslední akce" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mé akce" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nic" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s: přidat" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Zapomněl(a) jste své heslo?" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Vítejte," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Smazat" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Mazání %(object_name)s '%(escaped_object)s' by vyústilo ve vymazání souvisejících objektů, ale Váš účet nemá oprávnění pro mazání následujících typů objektů:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? All of the following related items will be deleted:" +msgstr "Jste si jist(á), že chcete smazat %(object_name)s \"%(escaped_object)s\"? Všechny následující související položky budou smazány:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Ano, jsem si jist" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Dle %(filter_title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Provést" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 výsledek" +msgstr[1] "%(counter)s výsledky" +msgstr[2] "%(counter)s výsledků" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "celkem %(full_result_count)s" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Zobrazit všechny" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtr" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Pohled na stránku" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Prosíme, odstraňte chybu uvedenou níže." +msgstr[1] "Prosíme, odstraňte chyby uvedené níže." +msgstr[2] "Prosíme, odstraňte chyby uvedené níže." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Objednávání" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Objednávka:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Uložit jako nové" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Uložit a přidat další" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Uložit a pokračovat v úpravách" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Uložit" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." +msgstr "Něco není v pořádku s Vaší instalací databáze. Ujistěte se, že byly vytvořeny odpovídající tabulky databáze a že databáze je přístupná pro čtení daným databázovým uživatelem." + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "First, enter a username and password. Then, you'll be able to edit more user options." +msgstr "Nejdříve vložte uživatelské jméno a heslo. Poté budete moci upravovat více uživatelských možností." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Uživatelské jméno" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Heslo" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Heslo (znova)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Pro ověření vložte stejné heslo znovu." + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Změna hesla" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Změna hesla byla úspěšná" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Vaše heslo bylo změněno." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Obnovení hesla" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." +msgstr "Zapomněl(a) jste heslo? Vložte níže Vaši e-mailovou adresu a my Vaše heslo obnovíme a zašleme Vám e-mailem nové." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-mailová adresa:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Obnovit mé heslo" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Děkujeme Vám za Váš strávený čas na našich webových stránkách." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Přihlašte se znova" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Obnovení hesla bylo úspěšné" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." +msgstr "Poslali jsme Vám e-mailem nové heslo na adresu, kterou jste zadal(a). Měl(a) byste ji dostat během okamžiku." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." +msgstr "Vložte svoje staré heslo a poté vložte dvakrát nové heslo. Tak můžeme ověřit, že jste ho napsal(a) správně." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Staré heslo:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nové heslo:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Potvrdit heslo:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Změnit mé heslo" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Dostal(a) jste tento e-mail, protože jste požádal(a) o obnovení hesla" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "pro Váš uživatelský účet na %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Vaše nové heslo je: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Můžete změnit toto heslo na následující stránce: " + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Vaše uživatelské jméno, pro případ, že jste zapomněl(a):" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Děkujeme za používání našeho webu!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Tým %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklety" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Dokumentační bookmarklety" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                \n" +msgstr "" +"\n" +"

                Pro nainstalování bookmarkletů, přetáhněte odkaz na Vaše záložky (oblíbené),\n" +"nebo klikněte pravým tlačítkem na odkaz a přidejte ho k Vašim záložkám (oblíbeným). Nyní můžete\n" +"zvolit bookmarklet z libovolné stránky. Poznámka: Některé tyto\n" +"bookmarklety vyžadují, abyste prohlížel(a) stránky z počítače, který je nastaven jako\n" +"\"interní\" (promluvte si s Vaším administrátorem, jestli si nejste jisti,\n" +"zda je Váš počítač \"interní\").

                \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dokumentace pro tuto stránku" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "Z libovolné stránky otevře dokumentaci pro pohled, který vygeneroval tuto stránku." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Ukázat id objektu" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "Ukáže content-type a unikátní ID pro stránky, které reprezentují jeden objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Upravit tento objekt (ve stávajícím okně)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Přepne do admin stránky pro stránky, které reprezentují jeden objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Upravit tento objekt (ve novém okně)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Jako výše, ale otevře admin stránky v novém okně." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Datum:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Čas:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Momentálně:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Změna:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "přesměrovat z" + +#: contrib/redirects/models.py:8 +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "Toto by měla být absolutní cesta, bez domény. Např. '/udalosti/hledat/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "přesměrovat na" + +#: contrib/redirects/models.py:10 +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "Toto může být buď absolutní cesta (jako nahoře) nebo plné URL začínající na 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "přesměrovat" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "přesměrování" + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Příklad: '/o/kontakt/'. Ujistěte se, že máte počáteční a konečná lomítka." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titulek" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "obsah" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "povolit komentáře" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "jméno šablony" + +#: contrib/flatpages/models.py:13 +msgid "Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'." +msgstr "Například: 'flatpages/kontaktni_stranka.html'. Pokud toto není zadáno, systém použije 'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "nutná registrace" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Pokud je zaškrtnuto, pouze přihlášení uživatelé budou moci prohlížet tuto stránku." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "statická stránka" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "statické stránky" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Odhlášeno" + +#: contrib/auth/models.py:38 +#: contrib/auth/models.py:57 +msgid "name" +msgstr "jméno" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "codename" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "oprávnění" + +#: contrib/auth/models.py:43 +#: contrib/auth/models.py:58 +msgid "permissions" +msgstr "oprávnění" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "skupina" + +#: contrib/auth/models.py:61 +#: contrib/auth/models.py:100 +msgid "groups" +msgstr "skupiny" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "uživatelské jméno" + +#: contrib/auth/models.py:90 +msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." +msgstr "Požadováno. 30 znaků nebo méně. Pouze alfanumerické znaky (znaky, čísla a podtržítka)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "křestní jméno" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "příjmení" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "e-mailová adresa" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "heslo" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Použijte '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "administrativní přístup " + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Rozhodne, zda se může uživatel přihlásit do správy webu." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "aktivní" + +#: contrib/auth/models.py:96 +msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." +msgstr "Rozhodne, zda se může uživatel přihlásit do správy webu. Nastavte toto místo mazání účtů." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "stav superuživatel" + +#: contrib/auth/models.py:97 +msgid "Designates that this user has all permissions without explicitly assigning them." +msgstr "Stanoví, že tento uživatel má veškerá oprávnění bez jejich explicitního přiřazení." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "poslední přihlášení" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "datum zaregistrování" + +#: contrib/auth/models.py:101 +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "Kromě manuálně přidělených oprávnění uživatel dostane všechna oprávnění pro každou skupinu, ve které je." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "uživatelskí oprávnění" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "uživatel" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "uživatelé" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Osobní informace" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Oprávnění" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Důležitá data" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Skupiny" + +#: contrib/auth/models.py:256 +msgid "message" +msgstr "zpráva" + +#: contrib/auth/forms.py:52 +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "Váš prohlížeč pravděpodobně nemá zapnuté cookies. Cookies jsou potřeba pro zalogování." + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "Tento účet není aktivní." + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "jméno modelu Pythonu" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "typ obsahu" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "typy obsahu" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "klíč sezení" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "data sezení" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "datum expirace" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sezení" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sezení" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "jméno domény" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "zobrazené jméno" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "web" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "weby" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Pondělí" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Úterý" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Středa" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Čtvrtek" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Pátek" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sobota" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Neděle" + +#: utils/dates.py:14 +msgid "January" +msgstr "Leden" + +#: utils/dates.py:14 +msgid "February" +msgstr "Únor" + +#: utils/dates.py:14 +#: utils/dates.py:27 +msgid "March" +msgstr "Březen" + +#: utils/dates.py:14 +#: utils/dates.py:27 +msgid "April" +msgstr "Duben" + +#: utils/dates.py:14 +#: utils/dates.py:27 +msgid "May" +msgstr "Květen" + +#: utils/dates.py:14 +#: utils/dates.py:27 +msgid "June" +msgstr "Červen" + +#: utils/dates.py:15 +#: utils/dates.py:27 +msgid "July" +msgstr "Červenec" + +#: utils/dates.py:15 +msgid "August" +msgstr "Srpen" + +#: utils/dates.py:15 +msgid "September" +msgstr "Září" + +#: utils/dates.py:15 +msgid "October" +msgstr "Říjen" + +#: utils/dates.py:15 +msgid "November" +msgstr "Listopad" + +#: utils/dates.py:16 +msgid "December" +msgstr "Prosinec" + +#: utils/dates.py:19 +msgid "jan" +msgstr "led" + +#: utils/dates.py:19 +msgid "feb" +msgstr "úno" + +#: utils/dates.py:19 +msgid "mar" +msgstr "bře" + +#: utils/dates.py:19 +msgid "apr" +msgstr "dub" + +#: utils/dates.py:19 +msgid "may" +msgstr "kvě" + +#: utils/dates.py:19 +msgid "jun" +msgstr "čen" + +#: utils/dates.py:20 +msgid "jul" +msgstr "čec" + +#: utils/dates.py:20 +msgid "aug" +msgstr "srp" + +#: utils/dates.py:20 +msgid "sep" +msgstr "zář" + +#: utils/dates.py:20 +msgid "oct" +msgstr "říj" + +#: utils/dates.py:20 +msgid "nov" +msgstr "lis" + +#: utils/dates.py:20 +msgid "dec" +msgstr "pro" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Led." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Ún." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Srp." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Zář." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Říj." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "List." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Pros." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "rok" +msgstr[1] "roky" +msgstr[2] "let" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "měsíc" +msgstr[1] "měsíce" +msgstr[2] "měsíců" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "týden" +msgstr[1] "týdny" +msgstr[2] "týdnů" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "den" +msgstr[1] "dny" +msgstr[2] "dnů" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hodina" +msgstr[1] "hodiny" +msgstr[2] "hodin" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuta" +msgstr[1] "minuty" +msgstr[2] "minut" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j.n.Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j.n.Y, H:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "j. F" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Arabic" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengálsky" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "Česky" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "Welšsky" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "Dánsky" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "Německy" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "Řecky" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "Anglicky" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "Španělsky" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Argentinean Spanish" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "Finsky" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "Francouzsky" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "Galicijsky" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "Maďarsky" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "Hebrejsky" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "Islandština" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "Italsky" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "Japonština" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "Holandština" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "Norsky" + +#: conf/global_settings.py:59 +msgid "Brazilian" +msgstr "Brazilsky" + +#: conf/global_settings.py:60 +msgid "Romanian" +msgstr "Rumunsky" + +#: conf/global_settings.py:61 +msgid "Russian" +msgstr "Rusky" + +#: conf/global_settings.py:62 +msgid "Slovak" +msgstr "Slovensky" + +#: conf/global_settings.py:63 +msgid "Slovenian" +msgstr "Slovinsky" + +#: conf/global_settings.py:64 +msgid "Serbian" +msgstr "Srbsky" + +#: conf/global_settings.py:65 +msgid "Swedish" +msgstr "Švédsky" + +#: conf/global_settings.py:66 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:67 +msgid "Turkish" +msgstr "Turecky" + +#: conf/global_settings.py:68 +msgid "Ukrainian" +msgstr "Ukrajinsky" + +#: conf/global_settings.py:69 +msgid "Simplified Chinese" +msgstr "Jednoduchá čínština" + +#: conf/global_settings.py:70 +msgid "Traditional Chinese" +msgstr "Tradiční čínština" + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Tato hodnota musí obsahovat pouze znaky, čísla nebo podtržítka." + +#: core/validators.py:67 +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "Tato hodnota musí obsahovat pouze znaky, čísla, podtržítka, pomlčky nebo lomítka." + +#: core/validators.py:71 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Tato hodnota musí obsahovat pouze znaky, čísla, podtržítka nebo čárky." + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "Velká písmena zde nejsou povolená." + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "Malá písmena zde nejsou povolená." + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "Vložte pouze cifry oddělené čárkami." + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Vložte platné e-mailové adresy oddělené čárkami." + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "Prosíme, zadejte platnou IP adresu." + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "Zde nejsou povolené prázdné hodnoty." + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "Znaky, které nejsou čísla, nejsou zde povoleny." + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "Tato hodnota nemůže být složená pouze z cifer." + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "Vložte celé číslo." + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "Zde jsou povoleny pouze alfanumerické znaky." + +#: core/validators.py:138 +msgid "Year must be 1900 or later." +msgstr "Rok musí být 1900 a vyšší." + +#: core/validators.py:142 +#, python-format +msgid "Invalid date: %s." +msgstr "Neplatné datum: %s." + +#: core/validators.py:146 +#: db/models/fields/__init__.py:415 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Vložte platné datum ve formátu RRRR-MM-DD." + +#: core/validators.py:151 +msgid "Enter a valid time in HH:MM format." +msgstr "Vložte platný čas ve formátu HH:MM." + +#: core/validators.py:155 +#: db/models/fields/__init__.py:477 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Vložte platné datum a čas ve formátu RRRR-MM-DD HH:MM." + +#: core/validators.py:160 +msgid "Enter a valid e-mail address." +msgstr "Vložte platnou e-mailovou adresu." + +#: core/validators.py:172 +#: core/validators.py:401 +#: forms/__init__.py:661 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Soubor nebyl odeslán. Zkontrolujte encoding type formuláře." + +#: core/validators.py:176 +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "Nahrajte na server platný obrázek. Soubor, který jste nahrál(a) nebyl obrázek, nebo byl porušen." + +#: core/validators.py:183 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "URL %s neukazuje na platný obrázek." + +#: core/validators.py:187 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Telefonní čísla musí být ve formátu XXX-XXX-XXXX. \"%s\" není platné." + +#: core/validators.py:195 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "URL %s neodkazuje na platné video ve formátu QuickTime." + +#: core/validators.py:199 +msgid "A valid URL is required." +msgstr "Je vyžadováno platné URL." + +#: core/validators.py:213 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Je vyžadováno platné HTML. Konkrétní chyby jsou:\n" +"%s" + +#: core/validators.py:220 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Špatně formované XML: %s" + +#: core/validators.py:230 +#, python-format +msgid "Invalid URL: %s" +msgstr "Neplatné URL: %s" + +#: core/validators.py:234 +#: core/validators.py:236 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Odkaz na URL %s je rozbitý." + +#: core/validators.py:242 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Vložte platnou zkraku U.S. státu." + +#: core/validators.py:256 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Mluvte slušně! Slovo %s zde není přípustné." +msgstr[1] "Mluvte slušně! Slova %s zde nejsou přípustná." +msgstr[2] "Mluvte slušně! Slova %s zde nejsou přípustná." + +#: core/validators.py:263 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Toto pole se musí shodovat s polem '%s'." + +#: core/validators.py:282 +msgid "Please enter something for at least one field." +msgstr "Prosíme, vložte něco alespoň pro jedno pole." + +#: core/validators.py:291 +#: core/validators.py:302 +msgid "Please enter both fields or leave them both empty." +msgstr "Prosíme, vložte obě pole, nebo je nechte obě prázdná." + +#: core/validators.py:309 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Toto pole musí být vyplněno, když %(field)s má %(value)s" + +#: core/validators.py:321 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Toto pole musí být vyplněno, když %(field)s nemá %(value)s" + +#: core/validators.py:340 +msgid "Duplicate values are not allowed." +msgstr "Duplikátní hodnoty nejsou povolené." + +#: core/validators.py:363 +#, python-format +msgid "This value must be a power of %s." +msgstr "Tato hodnota musí být mocninou %s." + +#: core/validators.py:374 +msgid "Please enter a valid decimal number." +msgstr "Prosíme, vložte platné číslo." + +#: core/validators.py:378 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Prosíme, vložte platné číslo s nejvíce %s cifrou celkem." +msgstr[1] "Prosíme, vložte platné číslo s nejvíce %s ciframi celkem." +msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi celkem." + +#: core/validators.py:381 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Prosíme, vložte platné číslo s nejvíce %s cifrou." +msgstr[1] "Prosíme, vložte platné číslo s nejvíce %s ciframi." +msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi." + +#: core/validators.py:384 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Prosíme, vložte platné číslo s nejvíce %s cifrou za desetinnou čárkou celkem." +msgstr[1] "Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou celkem." +msgstr[2] "Prosíme, vložte platné číslo s nejvíce %s ciframi za desetinnou čárkou celkem." + +#: core/validators.py:394 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Ujistěte se, že posílaný soubor je velký nejméně %s bytů." + +#: core/validators.py:395 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Ujistěte se, že posílaný soubor je velký nejvíce %s bytů." + +#: core/validators.py:412 +msgid "The format for this field is wrong." +msgstr "Formát pro toto pole je špatný." + +#: core/validators.py:427 +msgid "This field is invalid." +msgstr "Toto pole není platné." + +#: core/validators.py:463 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Nemohl jsem získat nic z %s." + +#: core/validators.py:466 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "URL %(url)s vrátilo neplatnou hlavičku Content-Type '%(contenttype)s'." + +#: core/validators.py:499 +#, python-format +msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgstr "Prosíme, zavřete nezavřenou značku %(tag)s z řádky %(line)s. (Řádka začíná s \"%(start)s\".)" + +#: core/validators.py:503 +#, python-format +msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" +msgstr "Nějaký text začínající na řádce %(line)s není povolen v tomto kontextu. (Řádka začíná s \"%(start)s\".)" + +#: core/validators.py:508 +#, python-format +msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" +msgstr "\"%(attr)s\" na řádce %(line)s je neplatný atribut. (Řádka začíná s \"%(start)s\".)" + +#: core/validators.py:513 +#, python-format +msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" +msgstr "\"<%(tag)s>\" na řádce %(line)s je neplatná značka. (Řádka začíná s \"%(start)s\".)" + +#: core/validators.py:517 +#, python-format +msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" +msgstr "Značce na řádce %(line)s schází jeden nebo více požadovaných atributů. (Řádka začíná s \"%(start)s\".)" + +#: core/validators.py:522 +#, python-format +msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" +msgstr "Atribut \"%(attr)s\" na řádce %(line)s má neplatnou hodnotu. (Řádka začína s \"%(start)s\".)" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "Záznam %(verbose_name)s byl úspěšně vytvořen." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "Záznam %(verbose_name)s byl úspěšně změnen." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "Záznam %(verbose_name)s byl smazán." + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s s tímto %(type)s již existují pro daná %(field)s." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s s tímto %(fieldname)s již existuje." + +#: db/models/fields/__init__.py:114 +#: db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:551 +#: db/models/fields/__init__.py:562 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Toto pole je povinné." + +#: db/models/fields/__init__.py:340 +msgid "This value must be an integer." +msgstr "Tato hodnota musí být celé číslo." + +#: db/models/fields/__init__.py:372 +msgid "This value must be either True or False." +msgstr "Tato hodnota musí být buď Ano (True), nebo Ne (False)." + +#: db/models/fields/__init__.py:388 +msgid "This field cannot be null." +msgstr "Toto pole nemůže být prázdné (null)." + +#: db/models/fields/__init__.py:571 +msgid "Enter a valid filename." +msgstr "Vložte platný název souboru." + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "Prosíme, zadejte %s správně." + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "Oddělte více identifikátorů čárkami." + +#: db/models/fields/related.py:620 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Podržte \"Control\", nebo \"Command\" na Macu pro vybrání více jak jedné položky." + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Prosíme, vložte platná %(self)s ID. Hodnota %(value)r není platná." +msgstr[1] "Prosíme, vložte platná %(self)s ID. Hodnoty %(value)r nejsou platné." +msgstr[2] "Prosíme, vložte platná %(self)s ID. Hodnoty %(value)r nejsou platné." + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Ujistěte se, že Váš text má méně než %s znak." +msgstr[1] "Ujistěte se, že Váš text má méně než %s znaky." +msgstr[2] "Ujistěte se, že Váš text má méně než %s znaků." + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "Zalomení řádky zde nenjsou povolená." + +#: forms/__init__.py:487 +#: forms/__init__.py:560 +#: forms/__init__.py:599 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Vyberte platnou volbu. '%(data)s' není mezi %(choices)s." + +#: forms/__init__.py:663 +msgid "The submitted file is empty." +msgstr "Odevzdaný soubor je prázdný." + +#: forms/__init__.py:719 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Vložte celé číslo mezi -32,768 a 32,767." + +#: forms/__init__.py:729 +msgid "Enter a positive number." +msgstr "Vložte celé kladné číslo." + +#: forms/__init__.py:739 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Vložte celé číslo mezi 0 a 32,767." + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "ano, ne, možná" + diff --git a/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..097bd199d8e26f0c5eabd6621acc5024e380afb1 GIT binary patch literal 1458 zcwSwTJ!~9B6o3bFhv5PN45A9sqllFV*6dz_iOxBYeRdSvXZvJ378shj-Iw#&do!n* z*@Hc$KvYN&i2@`{3S=oASTvNO;d~WR1VtnYL_tSGK?fCYcGosxrJ3)&nfE*Q?zf53 z=ZwDRX`Q2WkJgK{-hG5+-%rHHh(8k_CH_KulK3m}apE7v{a?hVh<_6+#PKo4o*+&X zJVz`OCy6(RuZ-pX9}-K%kH_-(pN{46J|hD0PT_a2u>YZm_apH><$6FIC;e_I=QqT& z#P3RZ9m7%{?|vz-`vLI`@%Ms%7U%yIcH@-n9pc$?9&fjxF6VU&3jG7(3&fAgdHr9N zbN{;qzb3jTba&6Ohrjaw^rvY(!}9)JZ_l#)EnUAZ7qytx=;FHGCjR5=dOTCe?l)x9 zz^A%XY&MG6Y@dsmw_}9!hBbJMGUA#98Dh*DJ4zV@4pwyWM2oWvgEDi8k?Z^{R^c97 z;xgeng=^T>nXtljxC662En<>WSV|<)7N;1A7*`ZPc(9TIIN^`uV{;hMB-t zL?lIL$FfzWByI1pb?lNA+rSt@3#m#pggZ)vc)?r*E+YtI5$*xoR1r^U?<%dy?QJv` zwL5BQwM(wvV&rY1HbTi7%n4APB`Y+Xk0Nw;2YMK5kF+LbIrv@g+^Kr52RwP(Y}bdWy=r=(^CTx3OA<9 z*<=q7M*D1oUXu*BQW`9aaCDm+r)X}WKRUo%mc#Fz+ZG)_Kkf1Xg}m-wCuL2HX!M6) zN~K|Lw2uSQ=94z<%l)G}^rUd~3F`hSF@z1>BYoTDA0EQ!i{are=}lp*>hTC@7L6b~ xs{Y$pDx!)plE_w5IXq-*5xVkDN;ZQ2l6$e(lP({dmuT4b@Q|jIZ5`c4!oR(baLE7w literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..9143b56 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/cs/LC_MESSAGES/djangojs.po @@ -0,0 +1,112 @@ +# Translation of djangojs.po to Czech +# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the DJANGO package. +# Radek Svarz , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: Django JavaScript Czech translation\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-17 22:26+0100\n" +"PO-Revision-Date: 2006-05-03 12:04+0100\n" +"Last-Translator: \n" +"Language-Team: Czech\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Poedit-Language: Czech\n" +"X-Poedit-Country: CZECH REPUBLIC\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "K dispozici %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Vybrat vše" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Přidat" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Odebrat" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Vybraný %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Vyberte si a klikněte" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Vše vymazat" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "January February March April May June July August September October November December" +msgstr "Leden Únor Březen Duben Květen Červen Červenec Srpen Září Říjen Listopad Prosinec" + +#: contrib/admin/media/js/dateparse.js:27 +#, fuzzy +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Neděle Pondělí Úterý Středa Čtvrtek Pátek Sobota" + +#: contrib/admin/media/js/calendar.js:25 +#, fuzzy +msgid "S M T W T F S" +msgstr "N P U S C P S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Nyní" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Hodiny" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Vyberte čas" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Půlnoc" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 ráno" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Poledne" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Storno" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Dnes" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalendář" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Včera" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Zítra" + diff --git a/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..2c2f0751731c59dd9bcd7541d35aef201e18cbe8 GIT binary patch literal 22852 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 C$+s;4 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.po new file mode 100644 index 0000000..a0561e9 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/django.po @@ -0,0 +1,1990 @@ +# Translation of Django to Welsh. +# Copyright (C) 2005 Django. +# This file is distributed under the same license as the Django package. +# Jason Davies , 2005. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:12+0200\n" +"PO-Revision-Date: 2005-11-05 HO:MI+ZONE\n" +"Last-Translator: Jason Davies \n" +"Language-Team: Cymraeg \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID gwrthrych" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "pennawd" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "sylw" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "cyfraddiad #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "cyfraddiad #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "cyfraddiad #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "cyfraddiad #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "cyfraddiad #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "cyfraddiad #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "cyfraddiad #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "cyfraddiad #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "yn gyfraddiad dilys" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "dyddiad/amser wedi ymostwng" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "yn gyhoeddus" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "cyfeiriad IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "wedi diddymu" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "sylw" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Gwrthrych cynnwys" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Postiwyd gan %(user)s ar %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "enw'r person" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "cyfeiriad ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "Sylw rhydd" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "Sylwadau rhydd" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "sgôr" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "dyddiad sgôr" + +#: contrib/comments/models.py:237 +#, fuzzy +msgid "karma score" +msgstr "Sgôr Carma" + +#: contrib/comments/models.py:238 +#, fuzzy +msgid "karma scores" +msgstr "Sgorau Carma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "fflagio dyddiad" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "Fflag defnyddiwr" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "Fflagiau defnyddwyr" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Fflagio hefo %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "dyddiad dilead" + +#: contrib/comments/models.py:280 +#, fuzzy +msgid "moderator deletion" +msgstr "Dilead cymedrolwr" + +#: contrib/comments/models.py:281 +#, fuzzy +msgid "moderator deletions" +msgstr "Dileadau cymedrolwr" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Dilead cymedrolwr gan %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Ni gellir defnyddwyr dienw pleidleisio" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID sylw annilys" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Dim pleidleisio ar gyfer eich hun" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Caniateir POSTiau yn unig" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Enw defnyddiwr:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Cyfrinair:" + +#: contrib/comments/templates/comments/form.html:6 +#, fuzzy +msgid "Forgotten your password?" +msgstr "Newidio fy nghyfrinair" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Allgofnodi" + +#: contrib/comments/templates/comments/form.html:12 +#, fuzzy +msgid "Ratings" +msgstr "cyfraddiad #1" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +#, fuzzy +msgid "Comment:" +msgstr "Sylw" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +#, fuzzy +msgid "Preview comment" +msgstr "Sylw rhydd" + +#: contrib/comments/templates/comments/freeform.html:4 +#, fuzzy +msgid "Your name:" +msgstr "enw defnyddiwr" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                By %s:

                \n" +"
                  \n" +msgstr "" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Unrhyw dyddiad" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Heddiw" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "7 diwrnod gorffennol" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Mis yma" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Blwyddyn yma" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ie" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Na" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "amser gweithred" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id gwrthrych" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr gwrthrych" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "fflag gweithred" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "neges newid" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "cofnod" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "cofnodion" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Dyddiadau i gyd" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Mewngofnodi" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "" + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Gweinyddiad safle" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "" + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "" + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Ychwanegu %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Ychwanegwyd %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "ac" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Newidiwyd %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Dileuwyd %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "" + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "" + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Newidio %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Ydych yn sicr?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Hanes newid: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Dewis %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Dewis %s i newid" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Dyddiad (heb amser)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Dyddiad (gyda amser)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Cyfeiriad e-bost" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Llwybr ffeil" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Rhif degol" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Boole (Naill ai True, False neu None)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Perthynas i model rhiant" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Rhif ffôn" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Testun" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Amser" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Talaith U.D. (dwy briflythyren)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "Testun XML" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dogfennaeth" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Newid cyfrinair" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Adref" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Hanes" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dyddiad/amser" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Defnyddiwr" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Gweithred" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Does dim hanes newid gan y gwrthrych yma. Mae'n debyg ni ychwanegwyd drwy'r " +"safle gweinydd yma." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Gweinyddiad safle Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Gweinyddiad Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Gwall gweinyddwr" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Gwall gweinyddwr (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Gwall Gweinyddwr (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Mae gwall wedi digwydd. Adroddwyd i weinyddwyr y safle drwy e-bost ac ddylai " +"cael ei drwsio cyn bo hir." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Tudalen heb ei ddarganfod" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Mae'n ddrwg gennym, ond nid darganfwyd y dudalen a dymunwyd" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Ychwanegu" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Newidio" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Does genych ddim hawl i olygu unrhywbeth." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Gweithredau Diweddar" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Fy Ngweithredau" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Dim ar gael" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Ychwanegu %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Ydych wedi anghofio eich cyfrinair?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Croeso," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Dileu" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Bydda dileu'r %(object_name)s '%(object)s' yn ddilyn i dileu'r wrthrychau " +"perthynol, ond ni chaniateir eich cyfrif ddileu'r mathau o wrthrych canlynol:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Ydych yn sicr chi eiso ddileu'r %(object_name)s \"%(object)s\"? Bydd y cwbl " +"o'r eitemau perthynol canlynol yn cae eu ddileu:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Yndw, rwy'n sicr" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr " Gan %(title)s" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Ewch" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Gweld ar safle" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Trefnu" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Trefn:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Cadw fel newydd" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Cadw ac ychwanegu un arall" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Cadw ac parhau i olygu" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Cadw" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Newid cyfrinair" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Newid cyfrinair yn lwyddianus" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Newidwyd eich cyfrinair." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Ailosod cyfrinair" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Wedi anghofio eich cyfrinair? Rhowch eich cyfeiriad e-bost isod, ac " +"ailosodan eich cyfrinair ac e-bostio'r un newydd i chi." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Cyfeiriad e-bost:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Ailosodi fy nghyfrinair" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Diolch am dreulio amser ansawdd gyda'r safle we heddiw 'ma." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Ailmewngofnodi" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Ailosod cyfrinair yn lwyddianus" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "Wedi e-bostio cyfrinair newydd i'r gyfeiriad e-bost " + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Rhowch eich cyfrinair hen, er mwyn gwarchodaeth, yna rhowch eich cyfrinair " +"newydd dwywaith er mwyn i ni wirio y teipiwyd yn gywir." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Cyfrinair hen:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Cyfrinair newydd:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Cadarnhewch cyfrinair:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Newidio fy nghyfrinair" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Chi'n derbyn yr e-bost yma achos ddymunwyd ailosod cyfrinair" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "er mwyn eich cyfrif defnyddiwr ar %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Eich cyfrinair newydd yw: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Mae croeso i chi newid y gyfrinair hon wrth fynd i'r dudalen yma:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Eich enw defnyddiwr, rhag ofn chi wedi anghofio:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Diolch am ddefnyddio ein safle!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Y tîm %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Dalennau gofnod" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Dogfennaeth dalennau gofnod" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                  To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                  \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dogfennaeth er mwyn y dudalen yma" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Dangos ID gwrthrych" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Yn dangos y fath-cynnwys a'r ID unigryw ar gyfer tudalennau sy'n " +"cynrychioligwrthrych sengl." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Golygu'r gwrthrych yma (ffenestr cyfoes)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Yn neidio i'r dudalen weinyddiad ar gyfer tudalennau sy'n cynrychioli " +"gwrthrych sengl." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Golygu'r gwrthrych yma (ffenestr newydd)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Fel uwchben, ond yn agor y dudalen weinyddiad mewn ffenestr newydd." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Dyddiad:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Amser:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "" + +#: contrib/admin/templates/widget/file.html:3 +#, fuzzy +msgid "Change:" +msgstr "Newidio" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "ailgyfeirio o" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Ddylai hon bod yn lwybr hollol, heb y parth-enw. Er enghraifft: '/" +"digwyddiadau/chwilio/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "ailgyfeirio i" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Gellir fod naill ai llwybr hollol (fel uwch) neu URL hollol yn ddechrau â " +"'http://'." + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "ailgyfeiriad" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "ailgyfeiriadau" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Er enghraifft: '/amdan/cyswllt/'. Sicrhewch gennych slaesau arweiniol ac " +"trywyddiol." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "teitl" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "cynnwys" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "galluogi sylwadau" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "enw'r templed" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Er enghraifft: 'flatpages/tudalen_cyswllt'. Os nid darparwyd, ddefnyddia'r " +"system 'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "cofrestriad gofynnol" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Os wedi dewis, dim ond defnyddwyr a mewngofnodwyd bydd yn gallu gweld y " +"tudalen." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "tudalen fflat" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "tudalennau fflat" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "enw" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "enw arwyddol" + +#: contrib/auth/models.py:17 +#, fuzzy +msgid "permission" +msgstr "Hawl" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +#, fuzzy +msgid "permissions" +msgstr "Hawliau" + +#: contrib/auth/models.py:29 +#, fuzzy +msgid "group" +msgstr "Grŵp" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +#, fuzzy +msgid "groups" +msgstr "Grwpiau" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "enw defnyddiwr" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "enw cyntaf" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "enw olaf" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "cyfeiriad e-bost" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "cyfrinair" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Defnyddiwch '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "statws staff" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Dylunio ai'r defnyddiwr yn gally mewngofnodi i'r safle weinyddiad yma." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "gweithredol" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "statws defnyddiwr swper" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "mewngofnod olaf" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "Dyddiad" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Yn ogystal â'r hawliau trosglwyddwyd dros law, byddai'r defnyddiwr yma hefyd " +"yn cael y cwbl hawliau a addefwyd i pob grŵp mae o/hi mewn." + +#: contrib/auth/models.py:67 +#, fuzzy +msgid "user permissions" +msgstr "Hawliau" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "Defnyddiwr" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "Defnyddwyr" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Gwybodaeth personol" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Hawliau" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Dyddiadau pwysig" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grwpiau" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "Neges" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" + +#: contrib/contenttypes/models.py:25 +#, fuzzy +msgid "python model class name" +msgstr "enw modwl python" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "math cynnwys" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "mathau cynnwys" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "goriad sesiwn" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "data sesiwn" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "dyddiad darfod" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "sesiwn" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sesiynau" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "parth-enw" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "enw arddangos" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "safle" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "safleoedd" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Dydd Llun" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Dydd Mawrth" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Dydd Mercher" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Dydd Iau" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Dydd Gwener" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Dydd Sadwrn" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Dydd Sul" + +#: utils/dates.py:14 +msgid "January" +msgstr "Ionawr" + +#: utils/dates.py:14 +msgid "February" +msgstr "Chwefror" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Mawrth" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Ebrill" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Mai" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Mehefin" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Gorffenaf" + +#: utils/dates.py:15 +msgid "August" +msgstr "Awst" + +#: utils/dates.py:15 +msgid "September" +msgstr "Medi" + +#: utils/dates.py:15 +msgid "October" +msgstr "Hydref" + +#: utils/dates.py:15 +msgid "November" +msgstr "Tachwedd" + +#: utils/dates.py:16 +msgid "December" +msgstr "Rhagfyr" + +#: utils/dates.py:19 +#, fuzzy +msgid "jan" +msgstr "ac" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +#, fuzzy +msgid "may" +msgstr "diwrnod" + +#: utils/dates.py:19 +msgid "jun" +msgstr "" + +#: utils/dates.py:20 +msgid "jul" +msgstr "" + +#: utils/dates.py:20 +msgid "aug" +msgstr "" + +#: utils/dates.py:20 +msgid "sep" +msgstr "" + +#: utils/dates.py:20 +msgid "oct" +msgstr "" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Ion." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Chwe." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Awst" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Medi" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Hyd." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Tach." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Rhag." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "blwyddyn" +msgstr[1] "blynyddoedd" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mis" +msgstr[1] "misoedd" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "diwrnod" +msgstr[1] "diwrnodau" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "awr" +msgstr[1] "oriau" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "munud" +msgstr[1] "munudau" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengaleg" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Tsieceg" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Cymraeg" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Daneg" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Almaeneg" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Saesneg" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Spaeneg" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Ffrangeg" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galisieg" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Islandeg" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Eidaleg" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norwyeg" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brasileg" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Romaneg" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Rwsieg" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Slofaceg" + +#: conf/global_settings.py:58 +#, fuzzy +msgid "Slovenian" +msgstr "Slofaceg" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Serbeg" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Swedeg" + +#: conf/global_settings.py:61 +#, fuzzy +msgid "Ukrainian" +msgstr "Brasileg" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Tsieinëeg Symledig" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Rhaid i'r werth yma cynnwys lythrennau, rhifau ac tanlinellau yn unig." + +#: core/validators.py:64 +#, fuzzy +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Rhaid i'r werth yma cynnwys lythrennau, rhifau, tanlinellau ac slaesau yn " +"unig." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Ni chaniateir priflythrennau yma." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Ni chaniateir lythrennau bach yma." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Rhowch digidau gwahanu gyda atalnodau yn unig." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Rhowch cyfeiriad e-bost dilys gwahanu gyda atalnodau." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Rhowch cyfeiriad IP dilys, os gwelwch yn dda." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Ni chaniateir gwerthau gwag yma." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Ni chaniateir nodau anrhifol yma." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Ni gellir y werth yma" + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Rhowch rhif cyfan." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Caniateir nodau gwyddorol un unig yma." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Rhowch dyddiad dilys mewn fformat YYYY-MM-DD." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Rhowch amser ddilys mewn fformat HH:MM." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Rhowch dyddiad/amser ddilys mewn fformat YYYY-MM-DD HH:MM." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Rhowch cyfeiriad e-bost ddilys." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Llwythwch delwedd dilys. Doedd y delwedd a llwythwyd dim yn ddelwedd dilys, " +"neu roedd o'n ddelwedd llwgr." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Dydy'r URL %s dim yn pwyntio at delwedd dilys." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Rhaid rifau ffon bod mewn fformat XXX-XXX-XXXX. Mae \"%s\" yn annilys." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Dydy'r URL %s dim yn pwyntio at fideo Quicktime dilys." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Mae URL dilys yn ofynnol." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Mae HTML dilys yn ofynnol. Gwallau penodol yw:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML wedi ffurfio'n wael: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL annilys: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Mae'r URL %s yn gyswllt toredig." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Rhowch talfyriad dalaith U.S. dilys." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Gwyliwch eich ceg! Ni chaniateir y gair %s yma." +msgstr[1] "Gwyliwch eich ceg! Ni chaniateir y geiriau %s yma." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Rhaid i'r faes yma cydweddu'r faes '%s'." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Rhowch rhywbeth am un maes o leiaf, os gwelwch yn dda." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Llenwch y ddwy faes, neu gadewch nhw'n wag, os gwelwch yn dda." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Rhaid roi'r faes yma os mae %(field)s yn %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Rhaid roi'r faes yma os mae %(field)s dim yn %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Ni chaniateir gwerthau ddyblyg." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Rhaid i'r gwerth yma fod yn bŵer o %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Rhowch rhif degol dilys, os gwelwch yn dda." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Rhowch rhif degol dilys gyda cyfanswm %s digidau o fwyaf." +msgstr[1] "Rhowch rhif degol dilys gyda cyfanswm %s digid o fwyaf." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Rhowch rif degol dilydd gyda o fwyaf %s lle degol, os gwelwch yn dda." +msgstr[1] "" +"Rhowch rif degol dilydd gyda o fwyaf %s lleoedd degol, os gwelwch yn dda." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Sicrhewch bod yr ffeil a llwythwyd yn o leiaf %s beit." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Sicrhewch bod yr ffeil a llwythwyd yn %s beit o fwyaf." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Mae'r fformat i'r faes yma yn anghywir." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Mae'r faes yma yn annilydd." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Ni gellir adalw unrhywbeth o %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"Dychwelodd yr URL %(url)s y pennawd Content-Type annilys '%(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Caewch y tag anghaedig %(tag)s o linell %(line)s. (Linell yn ddechrau â \"%" +"(start)s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Ni chaniateir rhai o'r destun ar linell %(line)s yn y gyd-destun yna. " +"(Linell yn ddechrau â \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"Mae \"%(attr)s\" ar lein %(line)s yn priodoledd annilydd. (Linell yn " +"ddechrau â \"%(start)s\".)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"Mae \"<%(tag)s>\" ar lein %(line)s yn tag annilydd. (Linell yn ddechrau â \"%" +"(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Mae tag ar lein %(line)s yn eisiau un new fwy priodoleddau gofynnol. (Linell " +"yn ddechrau â \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Mae gan y priodoledd \"%(attr)s\" ar lein %(line)s gwerth annilydd. (Linell " +"yn ddechrau â \"%(start)s\".)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Mae angen y faes yma." + +#: db/models/fields/__init__.py:337 +#, fuzzy +msgid "This value must be an integer." +msgstr "Rhaid i'r gwerth yma fod yn bŵer o %s." + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "Rhaid i'r gwerth yma fod yn bŵer o %s." + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "Mae'r faes yma yn annilydd." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Rhowch enw ffeil dilys." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Rhowch %s dilys, os gwelwch yn dda." + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr " Gwahanwch mwy nag un ID gyda atalnodau." + +#: db/models/fields/related.py:581 +#, fuzzy +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Gafaelwch lawr \"Control\", neu \"Command\" ar Fac, i ddewis mwy nag un." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Ni chaniateir toriadau llinell yma." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Dewisiwch dewis dilys; dydy '%(data)s' dim mewn %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "Mae'r ffeil yn wag." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Rhowch rhif cyfan rhwng -32,768 a 32,767." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Rhowch rhif positif." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Rhowch rhif cyfan rhwng 0 a 32,767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "ie,na,efallai" + +#~ msgid "Comment" +#~ msgstr "Sylw" + +#~ msgid "Comments" +#~ msgstr "Sylwadau" + +#~ msgid "String (up to 50)" +#~ msgstr "Llinyn (i fyny at 50)" + +#~ msgid "label" +#~ msgstr "label" + +#~ msgid "package" +#~ msgstr "pecyn" + +#~ msgid "packages" +#~ msgstr "pecynau" + +#, fuzzy +#~ msgid "count" +#~ msgstr "cynnwys" diff --git a/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/cy/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..c35e11bb18f89e1c9e02f49c6894332996b8af30 GIT binary patch literal 1009 zcwTLhJ#W-N5Qdjr!p8+tKua-wG8ydE!x{>idFtL>v*D#CP@n`+EOlqk7Y)M%C|oqq_G;z5k0y*G+pnyUD8g7TBe^ zxm~TBZ@$jCnO#43=Ukneac%{8D{HY1Pf-bOC{Q7$tdnS+BXF=XL)J-k_=@dvRd7?n zHjd0R7;qCNusJp|rMQIMLLs@dgv}x@atkLowwR63z(HuW6D5K9-}uz zo(0e;Gs7{4wXobsWlNiQy*cO)wk}`QZu!lQRu&cAR*q>Li?8jyamuB71YyEWj`ne3 zBX9Mx&P^4e@wQZ`g;a3>t0QTfBzV~xT?lSiR|-*P1?%EO=D@R@Byn=HzE*r&(w22~Aibs?Z5_+XD7{QQ-bXEvBPIw%ZhV{5oq`cDk hMJP%k*giET&G)D&eSO#=(R*}GlGu?pCF=@}>>vI+^_l, 2005. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2005-12-04 16:53+0000\n" +"Last-Translator: Jason Davies \n" +"Language-Team: Cymraeg \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:41 +#, fuzzy +msgid "Choose all" +msgstr "Dewis amser" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Ionawr Chwefror Mawrth Ebrill Mai Mehefin Gorffennaf Medi Hydref Tachwedd " +"Rhagfyr" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "S Ll M M I G S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Nawr" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Cloc" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Dewis amser" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Hanner nos" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 y.b." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Hanner dydd" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Diddymu" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Heddiw" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendr" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Ddoe" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Yfory" diff --git a/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..9f86e56407653f78b699b4e95b2a4df457b2e6ae GIT binary patch literal 32010 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.po new file mode 100644 index 0000000..7688907 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/da/LC_MESSAGES/django.po @@ -0,0 +1,1927 @@ +# translation of django.po to Dansk +# Rune Rønde Laursen , 2006. +# Copyright (C) 2005 and beyond +# This file is distributed under the same license as the PACKAGE package. +# Morten Bagai , Nov 2005. +# Rune Rønde Laursen , Sept 2006. +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:12+0200\n" +"PO-Revision-Date: 2006-09-24 10:34+0200\n" +"Last-Translator: Rune Rønde Laursen \n" +"Language-Team: Dansk \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "objekt ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "overskrift" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "kommentar" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "rangering # 1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "rangering # 2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "rangering # 3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "rangering # 4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "rangering # 5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "rangering # 6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "rangering # 7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "rangering # 8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "er gyldig rangering" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "dato/tidspunkt oprettet" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "er offentlig" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "IP-adresse" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "er fjernet" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "Afkryds denne boks hvis kommentaren er upassende. Beskeden \"Denne kommentar er blevet fjernet\" vil blive vist istedet." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "kommentarer" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Indholdsobjekt" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Indsendt af %(user)s den %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "personens navn" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP-adresse" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "godkendt af personale" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "fri kommentar" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "frie kommentarer" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "score" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "scoringsdato" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "karma score" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karma score" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d rangering efter %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Denne kommentar blev markeret af %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "mærkedato" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "bruger-mærke" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "bruger-mærker" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Mærket af %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "sletningsdato" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "moderator-sletning" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "moderator-sletninger" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Moderator-sletning af %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonyme brugere kan ikke stemme" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Ugyldigt kommentar-ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Du kan ikke selv stemme" + +#: contrib/comments/views/comments.py:28 +msgid "This rating is required because you've entered at least one other rating." +msgstr "Denne rangering er påkrævet fordi du har indtastet mindst en anden rangering." + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Denne kommentar blev indsendt af en bruger som har indsendt færre end %(count)s kommentar:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Denne kommentar blev indsendt af en bruger som har indsendt færre end %(count)s kommentarer:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Denne kommentar blev indsendt af en overfladisk bruger:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Kun POST er tilladt" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "En eller flere af de påkrævede felter blev ikke indsendt" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Nogen har misbrugt kommentarformularen (sikkerhedsovertrædelse)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "Kommentarformularen havde en ugyldigt 'target'-parameter -- objekt-ID'var ugyldigt" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Kommentarformularen tilbød ikke hverken 'forhåndsvis' eller 'indsend'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Brugernavn:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Adgangskode:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Har du glemt dit kodeord?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Log ud" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Rangeringer" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Påkrævet" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Valgfri" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Indsend et foto" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Kommentar:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Forhåndsvis kommentar" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Dit navn:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                  By %s:

                  \n" +"
                    \n" +msgstr "" +"

                    Af %s:

                    \n" +"
                      \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Alle" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Når som helst" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Idag" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "De sidste 7 dage" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Denne måned" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Dette år" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ja" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nej" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Ukendt" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "handlingstid" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "objekt-ID" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "objekt repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "handlingsflag" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "ændringsmeddelelse" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "logmeddelelse" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "logmeddelelser" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Alle datoer" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "Indtast venligst et korrekt brugernavn og kodeord. Læg mærke til at begge felter er versalfølsomme." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Log ind" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "Log venligst ind igen, da din session er udløbet. Der er ingen grund til bekymring, informationen du indsendte er blevet gemt." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "Det ser ud til din browser ikke er indstillet til at acceptere cookier. Slå venligst cookier til, genindlæs denne side og prøv igen." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Brugernavne kan ikke indeholde tegnet '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Din e-mail-adresse er ikke dit brugernavn. Prøv '%s' i stedet." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Website-administration" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" blev tilføjet i databasen." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Du kan redigere det igen herunder." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Du kan tilføje endnu en %s herunder." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Tilføj %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Tilføjede %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "og" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Ændrede %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Slettede %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Ingen filer ændret." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" blev ændret." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" blev tilføjet. Du kan redigere det igen herunder." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Ændr %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Et eller flere %(fieldname)s i %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Et eller flere %(fieldname)s i %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" blev slettet." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Er du sikker?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Ændringshistorik: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Vælg %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Vælg %s for at ændre" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Heltal" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Boolsk (enten \"true\" eller \"false\")" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Tekst (op til %(maxlength)s)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Kommaadskilte heltal" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Dato (uden tid)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Dato (med tid)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "E-mail-adresse" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Filsti" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Decimaltal" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Boolsk (enten \"true\", \"false\", eller \"none\")" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Relation-til-forælder-model" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Telefonnummer" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Tekst" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Tid" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Stat (i USA, to store bogstaver)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "XML tekst" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dokumentation" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Ændre adgangskode" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Hjem" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Historik" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dato/tid" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Bruger" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Funktion" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Dette objekt har ingen ændringshistorik. Det blev formentlig ikke tilføjet " +"via dette administrations-site" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django website-administration" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django administration" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Serverfejl" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Serverfejl (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Serverfejl (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "Der opstod en fejl. Fejlen er rapporteret til website-administratoren via e-mail, og vil blive rettet hurtigst muligt. Tak for din tålmodighed." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Siden blev ikke fundet" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Vi beklager, men den ønskede side kunne ikke findes" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modeller til rådighed i %(name)s applikationen." + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Tilføj" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Ændr" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Du har ikke rettigheder til at foretage ændringer." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Seneste handlinger" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mine handlinger" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ingen tilgængelige" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Tilføj %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Har du glemt din adgangskode?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Velkommen," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Slet" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Hvis du sletter %(object_name)s '%(object)s' vil du også slette relaterede " +"objekter, men du har ikke rettigheder til at slette følgende objekttyper:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "Er du sikker på du vil slette %(object_name)s \"%(object)s\"? Alle følgende relaterede objekter vil blive slettet:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Ja, jeg er sikker" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr " Efter %(title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Kør" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Se på website" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Ret venligst fejlen herunder." +msgstr[1] "Ret venligst fejlene herunder." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Rækkefølge" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Rækkefølge:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Gem som ny" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Gem og tilføj endnu en" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Gem og fortsæt med at redigere" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Gem" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Ændr adgangskode" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Adgangskoden blev ændret" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Din adgangskode blev ændret." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Nulstil adgangskode" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "Har du glemt din adgangskode? Indtast din e-mail-adresse herunder, så sender vi dig en ny adgangskode." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-mail-adresse:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Nulstil min adgangskode" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Tak for den kvalitetstid du brugte på websitet idag." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Log ind igen" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Adgangskoden blev nulstillet" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "Vi har sendt en ny adgangskode til din e-mail-adresse. Du skulle modtage den om ganske kort tid." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Indtast venligst din gamle adgangskode, for en sikkerheds skyld og indtast så " +"din nye adgangskode to gange, så vi kan være sikre på, at den er indtastet " +"korrekt." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Gammel adgangskode:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Ny adgangskode:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Bekræft ny adgangskode:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Ændr min adgangskode" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Du modtager denne e-mail, fordi du har bedt om at få nulstillet din adgangskode" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "til din brugerkonto ved %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Din nye adgangskode er: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Du kan ændre din adgangskode ved at gå til denne side:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "I det tilfælde at du har glemt dit brugernavn er det:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Tak fordi du brugte vores website!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Med venlig hilsen %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Documentation bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                      To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                      \n" +msgstr "" +"\n" +"

                      For at installere bookmarklets, træk linket til din bogmærkelinje\n, eller højreklik på linket og tilføj det til dine bogmærker. Du kan nu\n" +"markere bookmarkletten fra enhver side på websitet. Bid mærke i at nogle af disse \n" +"bookmarkletter kræver at du ser på websitet fra en computer der opfattes \n" +"som \"intern\" (tal med din systemadministrator, hvis du ikke er sikker på om\n" +"din computer er \"intern\").

                      \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dokumentation for denne side" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "Bringer dig fra en hvilken som helst side til dokumentationen for det view der genererer den pågældende side." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Vis objekt-ID" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "Viser indholdstypen og unikt ID for sider der repræsenterer et enkelt objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Redigér dette objekt (i det aktuelle vindue)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Springer til administrationssiden for sider der repræsenterer et enkelt objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Redigér dette objekt (i nyt vindue)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Som ovenfor, men åbner administrationssiden i et nyt vindue." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Dato:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Tid:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Nuværende:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Ændr:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "omadresser fra" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Dette skal være en absolut sti uden domænenavnet. For eksempel: '/nyheder/" +"søg/" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "omadresser til" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Dette kan enten være en absolut sti (som ovenfor), eller en komplet URL " +"startende med 'http://'" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "omadressering" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "omaddresseringer" + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Eksempel: '/om/kontakt/'. Vær sikker på at du har en skråstreg foran og bagved." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titel" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "indhold" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "tillad kommentarer" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "skabelonnavn" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "Eksempel: 'fladesider/kontakt_side'. Hvis dette ikke tilbydes, bruger systemet 'fladesider/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "registrering påkrævet" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Hvis denne boks er markeret, vil kun brugere der er logget ind, kunne se " +"siden." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "flad side" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "flade sider" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "navn" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "kodenavn" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "rettighed" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "rettigheder" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "gruppe" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "grupper" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "brugernavn" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "fornavn" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "efternavn" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "e-mail-adresse" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "adgangskode" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Brug '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "administrationsstatus" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Bestemmer om brugeren kan logge ind på dette administrationswebsite." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "aktiv" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "superbrugerstatus" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "sidst logget ind" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "registreringsdato" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Udover de rettigheder, der manuelt er tildelt brugeren, vil denne også " +"få alle rettigheder der er tildelt hver gruppe, brugeren er medlem af." + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "brugerrettigheder" + +#: contrib/auth/models.py:70 +msgid "user" +msgstr "bruger" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "brugere" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Personlig information" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Rettigheder" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Vigtige datoer" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grupper" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "meddelelse" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Din browser ser ud til ikke at have cookier aktiveret. Cookier er påkrævet for at kunne logge ind." + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "python model klassenavn" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "indholdstype" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "indholdstyper" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "sessionsnøgle" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "sessionsdata" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "udløbsdato" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "session" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sessioner" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domænenavn" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "vist navn" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "website" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "websites" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Mandag" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Tirsdag" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Onsdag" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Torsdag" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Fredag" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Lørdag" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Søndag" + +#: utils/dates.py:14 +msgid "January" +msgstr "Januar" + +#: utils/dates.py:14 +msgid "February" +msgstr "Februar" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marts" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "April" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maj" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Juni" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Juli" + +#: utils/dates.py:15 +msgid "August" +msgstr "August" + +#: utils/dates.py:15 +msgid "September" +msgstr "September" + +#: utils/dates.py:15 +msgid "October" +msgstr "Oktober" + +#: utils/dates.py:15 +msgid "November" +msgstr "November" + +#: utils/dates.py:16 +msgid "December" +msgstr "December" + +#: utils/dates.py:19 +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "apr" + +#: utils/dates.py:19 +msgid "may" +msgstr "maj" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "aug" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sept" + +#: utils/dates.py:20 +msgid "oct" +msgstr "okt" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dec" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Aug." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dec." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "år" +msgstr[1] "år" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "måned" +msgstr[1] "måneder" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "uge" +msgstr[1] "uger" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dag" +msgstr[1] "dage" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "time" +msgstr[1] "timer" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minut" +msgstr[1] "minutter" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengalsk" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Tjekkisk" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Walisisk" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Dansk" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Tysk" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "Græsk" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Engelsk" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Spansk" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Fransk" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galicisk" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "Ungarsk" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "Hebræisk" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Islandsk" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Italiensk" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japansk" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Hollandsk" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norsk" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brasiliansk" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Rumænsk" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Russisk" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Slovakisk" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "Slovensk" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Serbisk" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Svensk" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "Ukrainsk" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Simpel Kinesisk" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Traditionel Kinesisk" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Dette felt må kun indeholde bogstaver, tal og understreger." + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Dette felt må kun indeholde bogstaver, tal, understreger, streger eller skråstreger." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Store bogstaver er ikke tilladt her." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Små bogstaver er ikke tilladt her." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Indtast kun tal adskilt af kommaer." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Indtast gyldige e-mail-adresser adskilt af kommaer." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Indtast venligst en gyldig IP-adresse." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Dette felt kan ikke være tomt." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Der må kun være tal i dette felt." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Denne værdi kan ikke udelukkende bestå af tal." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Indtast et heltal." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Her er kun bogstaver tilladt." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Indtast en dato i ÅÅÅÅ-MM-DD format." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Indtast tid i TT:MM format." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Indtast dato og tid i ÅÅÅÅ-MM-DD TT:MM format." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Indtast en gyldig e-mail-adresse." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Indsend en billedfil. Filen du indsendte var enten ikke et billede eller en " +"ødelagt billedfil." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "URLen %s viser ikke til en gyldig billedfil." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Telefonnumre skal være i formatet XXXXXXXX. \"%s\" er ikke godkendt." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "URLen %s viser ikke til en gyldig QuickTime-film." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "En gyldig URL er påkrævet." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Gyldig HTML er påkrævet. Fejlene er:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Ugyldig XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ugyldig URL: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Denne URL %s linker ikke til en gyldig side eller fil." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Indtast en gyldig amerikansk statsforkortelse." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Var din mund! Ordet %s er ikke tilladt her." +msgstr[1] "Var din mund! Ordene %s er ikke tilladt her." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Dette felt skal matche '%s' feltet." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Indtast venligst noget, i mindst ét felt" + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Udfyld begge felter, eller lad dem begge være tomme." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Dette felt skal udfyldes, hvis %(field)s er lig %(value)s." + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Dette felt skal udfyldes, hvis %(field)s ikke er lig %(value)s." + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Identiske værdier er ikke tilladt her." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Denne værdi skal være en potens af %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Indtast venligst et gyldigt decimaltal." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Indtast et gyldigt decimaltal med maksimalt %s ciffer i alt." +msgstr[1] "Indtast et gyldigt decimaltal med maksimalt %s cifre i alt." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Indtast en gyldig decimal med maksimalt %s tal efter kommaet" +msgstr[1] "Indtast en gyldig decimal med maksimalt %s tal efter kommaet" + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Tjek at den indsendte fil er mindst %s bytes." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Tjek at den indsendte fil er maksimalt %s bytes." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Formatet i dette felt er forkert." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Dette felt er ugyldigt." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Kunne ikke finde noget i %s." + +#: core/validators.py:429 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "URLen %(url)s returnerede ikke en godkendt Content-Type header '%(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "Luk venligst %(tag)s på linje %(line)s. (Linjen starter med \"%(start)s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"En del af teksten som starter på linje %(line)s er ikke tilladt. (Linjen " +"starter med \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" på linje %(line)s er ikke en gyldig attribut. (Linjen starter " +"med \"%(start)s\".)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" på linje %(line)s er et ugyldigt tag. (Linjen starter med \"%" +"(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Et tag på linje %(line)s mangler en påkrævet attribut. (Linjen starter " +"med \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"\"%(attr)s\" attributten på linje %(line)s har en ugyldig værdi. (Linjen " +"starter med \"%(start)s\".)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s med denne %(type)s eksisterer allerede for den givne %(field)s." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s med dette %(fieldname)s eksisterer allerede." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Dette felt er påkrævet." + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "Denne værdi skal et heltal." + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "Denne værdi skal være enten true eller false." + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "Dette felt kan ikke være null." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Indtast et gyldigt filnavn." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Indtast venligst en gyldig %s." + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "Adskil flere ID'er med kommaer." + +#: db/models/fields/related.py:581 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Hold \"Kontrol\", eller \"Æbletasten\" på Mac nede, for at vælge mere end en." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Indtast venligst et gyldigt %(self)s-ID. Værdien %(value)r er ugyldig." +msgstr[1] "Indtast venligst gyldige %(self)s-ID'er. Værdierne %(value)r er ugyldige." + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Sørg for din tekst er kortere end %s tegn." +msgstr[1] "Sørg for din tekst er kortere end %s tegn." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Linjebrud er ikke tilladt her." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Markér et gyldigt valg; '%(data)s' er ikke i %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "Den indsendte fil er tom." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Indtast et heltal mellem -32,768 og 32,767." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Indtast et positivt tal." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Indtast et heltal mellem 0 og 32,767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "ja,nej,måske" + diff --git a/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..b5c518c13c70767944642d06e54dbaf01a65122c GIT binary patch literal 40423 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 F{|8`k$aeq$ literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..2f0991c --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,2225 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-05 02:56+0100\n" +"PO-Revision-Date: 2007-02-05 03:19+0100\n" +"Last-Translator: Dirk Eschler \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language-Team: \n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"X-Generator: KBabel 1.11.4\n" + +#: .\conf\global_settings.py:39 +msgid "Arabic" +msgstr "Arabisch" + +#: .\conf\global_settings.py:40 +msgid "Bengali" +msgstr "Bengali" + +#: .\conf\global_settings.py:41 +msgid "Czech" +msgstr "Tschechisch" + +#: .\conf\global_settings.py:42 +msgid "Welsh" +msgstr "Walisisch" + +#: .\conf\global_settings.py:43 +msgid "Danish" +msgstr "Dänisch" + +#: .\conf\global_settings.py:44 +msgid "German" +msgstr "Deutsch" + +#: .\conf\global_settings.py:45 +msgid "Greek" +msgstr "Griechisch" + +#: .\conf\global_settings.py:46 +msgid "English" +msgstr "Englisch" + +#: .\conf\global_settings.py:47 +msgid "Spanish" +msgstr "Spanisch" + +#: .\conf\global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Argentinisches Spanisch" + +#: .\conf\global_settings.py:49 +msgid "Finnish" +msgstr "Finnisch" + +#: .\conf\global_settings.py:50 +msgid "French" +msgstr "Französisch" + +#: .\conf\global_settings.py:51 +msgid "Galician" +msgstr "Galicisch" + +#: .\conf\global_settings.py:52 +msgid "Hungarian" +msgstr "Ungarisch" + +#: .\conf\global_settings.py:53 +msgid "Hebrew" +msgstr "Hebräisch" + +#: .\conf\global_settings.py:54 +msgid "Icelandic" +msgstr "Isländisch" + +#: .\conf\global_settings.py:55 +msgid "Italian" +msgstr "Italienisch" + +#: .\conf\global_settings.py:56 +msgid "Japanese" +msgstr "Japanisch" + +#: .\conf\global_settings.py:57 +msgid "Dutch" +msgstr "Holländisch" + +#: .\conf\global_settings.py:58 +msgid "Norwegian" +msgstr "Norwegisch" + +#: .\conf\global_settings.py:59 +msgid "Polish" +msgstr "Polnisch" + +#: .\conf\global_settings.py:60 +msgid "Brazilian" +msgstr "Brasilianisch" + +#: .\conf\global_settings.py:61 +msgid "Romanian" +msgstr "Rumänisch" + +#: .\conf\global_settings.py:62 +msgid "Russian" +msgstr "Russisch" + +#: .\conf\global_settings.py:63 +msgid "Slovak" +msgstr "Slowakisch" + +#: .\conf\global_settings.py:64 +msgid "Slovenian" +msgstr "Slowenisch" + +#: .\conf\global_settings.py:65 +msgid "Serbian" +msgstr "Serbisch" + +#: .\conf\global_settings.py:66 +msgid "Swedish" +msgstr "Schwedisch" + +#: .\conf\global_settings.py:67 +msgid "Tamil" +msgstr "Tamilisch" + +#: .\conf\global_settings.py:68 +msgid "Turkish" +msgstr "Türkisch" + +#: .\conf\global_settings.py:69 +msgid "Ukrainian" +msgstr "Ukrainisch" + +#: .\conf\global_settings.py:70 +msgid "Simplified Chinese" +msgstr "Vereinfachtes Chinesisch" + +#: .\conf\global_settings.py:71 +msgid "Traditional Chinese" +msgstr "Traditionelles Chinesisch" + +#: .\contrib\admin\filterspecs.py:40 +#, python-format +msgid "" +"

                      By %s:

                      \n" +"
                        \n" +msgstr "" +"

                        Nach %s:

                        \n" +"
                          \n" + +#: .\contrib\admin\filterspecs.py:70 +#: .\contrib\admin\filterspecs.py:88 +#: .\contrib\admin\filterspecs.py:143 +#: .\contrib\admin\filterspecs.py:169 +msgid "All" +msgstr "Alle" + +#: .\contrib\admin\filterspecs.py:109 +msgid "Any date" +msgstr "Alle Daten" + +#: .\contrib\admin\filterspecs.py:110 +msgid "Today" +msgstr "Heute" + +#: .\contrib\admin\filterspecs.py:113 +msgid "Past 7 days" +msgstr "Letzte 7 Tage" + +#: .\contrib\admin\filterspecs.py:115 +msgid "This month" +msgstr "Diesen Monat" + +#: .\contrib\admin\filterspecs.py:117 +msgid "This year" +msgstr "Dieses Jahr" + +#: .\contrib\admin\filterspecs.py:143 +#: .\newforms\widgets.py:162 +#: .\oldforms\__init__.py:572 +msgid "Yes" +msgstr "Ja" + +#: .\contrib\admin\filterspecs.py:143 +#: .\newforms\widgets.py:162 +#: .\oldforms\__init__.py:572 +msgid "No" +msgstr "Nein" + +#: .\contrib\admin\filterspecs.py:150 +#: .\newforms\widgets.py:162 +#: .\oldforms\__init__.py:572 +msgid "Unknown" +msgstr "Unbekannt" + +#: .\contrib\admin\models.py:16 +msgid "action time" +msgstr "Zeitpunkt der Aktion" + +#: .\contrib\admin\models.py:19 +msgid "object id" +msgstr "Objekt-ID" + +#: .\contrib\admin\models.py:20 +msgid "object repr" +msgstr "Objekt Darst." + +#: .\contrib\admin\models.py:21 +msgid "action flag" +msgstr "Aktionskennzeichen" + +#: .\contrib\admin\models.py:22 +msgid "change message" +msgstr "Änderungsmeldung" + +#: .\contrib\admin\models.py:25 +msgid "log entry" +msgstr "Logeintrag" + +#: .\contrib\admin\models.py:26 +msgid "log entries" +msgstr "Logeinträge" + +#: .\contrib\admin\templates\admin\404.html.py:4 +#: .\contrib\admin\templates\admin\404.html.py:8 +msgid "Page not found" +msgstr "Seite nicht gefunden" + +#: .\contrib\admin\templates\admin\404.html.py:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Es tut uns leid, aber die angeforderte Seite konnte nicht gefunden werden." + +#: .\contrib\admin\templates\admin\500.html.py:4 +#: .\contrib\admin\templates\admin\base.html.py:30 +#: .\contrib\admin\templates\admin\change_form.html.py:13 +#: .\contrib\admin\templates\admin\change_list.html.py:6 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:6 +#: .\contrib\admin\templates\admin\invalid_setup.html.py:4 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:12 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\logged_out.html.py:4 +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +msgid "Home" +msgstr "Start" + +#: .\contrib\admin\templates\admin\500.html.py:4 +msgid "Server error" +msgstr "Serverfehler" + +#: .\contrib\admin\templates\admin\500.html.py:6 +msgid "Server error (500)" +msgstr "Serverfehler (500)" + +#: .\contrib\admin\templates\admin\500.html.py:9 +msgid "Server Error (500)" +msgstr "Serverfehler (500)" + +#: .\contrib\admin\templates\admin\500.html.py:10 +msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." +msgstr "Ein Fehler ist aufgetreten. Dieser Fehler wurde an die Serververwalter per E-Mail weitergegeben und sollte bald behoben sein. Vielen Dank für Ihr Verständnis." + +#: .\contrib\admin\templates\admin\base.html.py:25 +msgid "Welcome," +msgstr "Willkommen," + +#: .\contrib\admin\templates\admin\base.html.py:25 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:9 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Documentation" +msgstr "Dokumentation" + +#: .\contrib\admin\templates\admin\base.html.py:25 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:9 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:15 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:46 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +msgid "Change password" +msgstr "Passwort ändern" + +#: .\contrib\admin\templates\admin\base.html.py:25 +#: .\contrib\admin\templates\admin\change_form.html.py:10 +#: .\contrib\admin\templates\admin\change_list.html.py:5 +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:3 +#: .\contrib\admin\templates\admin\object_history.html.py:3 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:9 +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:4 +#: .\contrib\admin\templates\admin_doc\index.html.py:4 +#: .\contrib\admin\templates\admin_doc\missing_docutils.html.py:4 +#: .\contrib\admin\templates\admin_doc\model_detail.html.py:3 +#: .\contrib\admin\templates\admin_doc\model_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\template_filter_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\template_tag_index.html.py:5 +#: .\contrib\admin\templates\admin_doc\view_detail.html.py:4 +#: .\contrib\admin\templates\admin_doc\view_index.html.py:5 +#: .\contrib\admin\templates\registration\password_change_done.html.py:3 +#: .\contrib\admin\templates\registration\password_change_form.html.py:3 +#: .\contrib\comments\templates\comments\form.html.py:6 +msgid "Log out" +msgstr "Abmelden" + +#: .\contrib\admin\templates\admin\base_site.html.py:4 +msgid "Django site admin" +msgstr "Django Systemverwaltung" + +#: .\contrib\admin\templates\admin\base_site.html.py:7 +msgid "Django administration" +msgstr "Django Verwaltung" + +#: .\contrib\admin\templates\admin\change_form.html.py:15 +#: .\contrib\admin\templates\admin\index.html.py:28 +msgid "Add" +msgstr "Hinzufügen" + +#: .\contrib\admin\templates\admin\change_form.html.py:21 +#: .\contrib\admin\templates\admin\object_history.html.py:5 +msgid "History" +msgstr "Geschichte" + +#: .\contrib\admin\templates\admin\change_form.html.py:22 +msgid "View on site" +msgstr "Im Web Anzeigen" + +#: .\contrib\admin\templates\admin\change_form.html.py:32 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Bitte den aufgeführten Fehler korrigieren." +msgstr[1] "Bitte die aufgeführten Fehler korrigieren." + +#: .\contrib\admin\templates\admin\change_form.html.py:50 +msgid "Ordering" +msgstr "Sortierung" + +#: .\contrib\admin\templates\admin\change_form.html.py:53 +msgid "Order:" +msgstr "Reihenfolge:" + +#: .\contrib\admin\templates\admin\change_list.html.py:12 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s hinzufügen" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:9 +#: .\contrib\admin\templates\admin\submit_line.html.py:3 +msgid "Delete" +msgstr "Löschen" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:14 +#, python-format +msgid "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Die Löschung des %(object_name)s '%(escaped_object)s' hätte die Löschung von abhängigen Daten zur Folge, aber Sie haben nicht die nötigen Rechte um die folgenden abhängigen Daten zu löschen:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:21 +#, python-format +msgid "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? All of the following related items will be deleted:" +msgstr "Sind Sie sicher, dass Sie %(object_name)s \"%(escaped_object)s\" löschen wollen? Es werden zusätzlich die folgenden abhängigen Daten mit gelöscht:" + +#: .\contrib\admin\templates\admin\delete_confirmation.html.py:26 +msgid "Yes, I'm sure" +msgstr "Ja, ich bin sicher" + +#: .\contrib\admin\templates\admin\filter.html.py:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Nach %(filter_title)s " + +#: .\contrib\admin\templates\admin\filters.html.py:4 +msgid "Filter" +msgstr "Filter" + +#: .\contrib\admin\templates\admin\index.html.py:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelle, die in der Anwendung %(name)s vorhanden sind." + +#: .\contrib\admin\templates\admin\index.html.py:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: .\contrib\admin\templates\admin\index.html.py:34 +msgid "Change" +msgstr "Ändern" + +#: .\contrib\admin\templates\admin\index.html.py:44 +msgid "You don't have permission to edit anything." +msgstr "Sie haben keine Berechtigung irgendwas zu ändern." + +#: .\contrib\admin\templates\admin\index.html.py:52 +msgid "Recent Actions" +msgstr "Kürzliche Aktionen" + +#: .\contrib\admin\templates\admin\index.html.py:53 +msgid "My Actions" +msgstr "Meine Aktionen" + +#: .\contrib\admin\templates\admin\index.html.py:57 +msgid "None available" +msgstr "Keine vorhanden" + +#: .\contrib\admin\templates\admin\invalid_setup.html.py:8 +msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." +msgstr "Etwas stimmt nicht mit der Datenbankkonfiguration. Bitte sicherstellen, das die richtigen Datenbanktabellen angelegt wurden und bitte sicherstellen, das die Datenbank vom verwendeten Datenbankbenutzer auch lesbar ist." + +#: .\contrib\admin\templates\admin\login.html.py:17 +#: .\contrib\comments\templates\comments\form.html.py:6 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Username:" +msgstr "Benutzername:" + +#: .\contrib\admin\templates\admin\login.html.py:20 +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Password:" +msgstr "Passwort:" + +#: .\contrib\admin\templates\admin\login.html.py:25 +#: .\contrib\admin\views\decorators.py:24 +msgid "Log in" +msgstr "Anmelden" + +#: .\contrib\admin\templates\admin\object_history.html.py:18 +msgid "Date/time" +msgstr "Datum/Zeit" + +#: .\contrib\admin\templates\admin\object_history.html.py:19 +msgid "User" +msgstr "Benutzer" + +#: .\contrib\admin\templates\admin\object_history.html.py:20 +msgid "Action" +msgstr "Aktion" + +#: .\contrib\admin\templates\admin\object_history.html.py:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. N Y, H:i" + +#: .\contrib\admin\templates\admin\object_history.html.py:36 +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "Dieses Objekt hat keine Änderungsgeschichte. Es wurde möglicherweise nicht über diese Verwaltungsseiten angelegt." + +#: .\contrib\admin\templates\admin\pagination.html.py:10 +msgid "Show all" +msgstr "Zeige alle" + +#: .\contrib\admin\templates\admin\search_form.html.py:8 +msgid "Go" +msgstr "Los" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "Ein Ergebnis" +msgstr[1] "%(counter)s Ergebnisse" + +#: .\contrib\admin\templates\admin\search_form.html.py:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s gesamt" + +#: .\contrib\admin\templates\admin\submit_line.html.py:4 +msgid "Save as new" +msgstr "Als neu sichern" + +#: .\contrib\admin\templates\admin\submit_line.html.py:5 +msgid "Save and add another" +msgstr "Sichern und neu hinzufügen" + +#: .\contrib\admin\templates\admin\submit_line.html.py:6 +msgid "Save and continue editing" +msgstr "Sichern und weiter bearbeiten" + +#: .\contrib\admin\templates\admin\submit_line.html.py:7 +msgid "Save" +msgstr "Sichern" + +#: .\contrib\admin\templates\admin\auth\user\add_form.html.py:6 +msgid "First, enter a username and password. Then, you'll be able to edit more user options." +msgstr "Zuerst einen Benutzer und ein Passwort eingeben. Danach können weitere Optionen für den Benutzer geändert werden." + +#: .\contrib\admin\templates\admin\auth\user\add_form.html.py:12 +msgid "Username" +msgstr "Benutzername" + +#: .\contrib\admin\templates\admin\auth\user\add_form.html.py:18 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:34 +msgid "Password" +msgstr "Passwort" + +#: .\contrib\admin\templates\admin\auth\user\add_form.html.py:23 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:39 +msgid "Password (again)" +msgstr "Passwort (wiederholen)" + +#: .\contrib\admin\templates\admin\auth\user\add_form.html.py:24 +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:40 +msgid "Enter the same password as above, for verification." +msgstr "Bitte das gleiche Passwort zur Überprüfung nochmal eingeben." + +#: .\contrib\admin\templates\admin\auth\user\change_password.html.py:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "Bitte geben Sie ein neues Passwort für den Benutzer %(username)s ein." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:5 +msgid "Documentation bookmarklets" +msgstr "Dokumentations-Bookmarklets" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:9 +msgid "" +"\n" +"

                          To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                          \n" +msgstr "" +"\n" +"

                          Um Bookmarklets zu installieren müssen diese Links in die\n" +"Browser-Werkzeugleiste gezogen werden, oder mittels rechter Maustaste in die\n" +"Bookmarks gespeichert werden. Danach können die Bookmarklets von jeder Seite\n" +"aufgerufen werden. Einige Bookmarklets sind für den Zugriff von 'internen'\n" +"Rechnern eingeschränkt. Falls nicht klar ist, ob ein Rechner als 'intern'\n" +"bewertet wird, bitte den Administrator fragen.

                          \n" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:19 +msgid "Documentation for this page" +msgstr "Dokumentation für diese Seite" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:20 +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "Springt von jeder Seite zu der Dokumentation für den View der diese Seite erzeugt." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:22 +msgid "Show object ID" +msgstr "Objekt-ID anzeigen" + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:23 +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "Zeigt den Content-Type und die eindeutige ID für Seiten die ein einzelnes Objekt repräsentieren." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:25 +msgid "Edit this object (current window)" +msgstr "Dieses Objekt im aktuellen Fenster ändern." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Springt zu der Administrationsseite für dieses Objekt, wenn diese Seite ein Objekt repräsentiert." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:28 +msgid "Edit this object (new window)" +msgstr "Dieses Objekt in einem neuen Fenster ändern." + +#: .\contrib\admin\templates\admin_doc\bookmarklets.html.py:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Wie zuvor, aber öffnet die Administrationsseite in einem neuen Fenster." + +#: .\contrib\admin\templates\registration\logged_out.html.py:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Vielen Dank, dass Sie hier ein paar nette Minuten verbracht haben." + +#: .\contrib\admin\templates\registration\logged_out.html.py:10 +msgid "Log in again" +msgstr "Erneut anmelden" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:4 +#: .\contrib\admin\templates\registration\password_change_form.html.py:6 +#: .\contrib\admin\templates\registration\password_change_form.html.py:10 +msgid "Password change" +msgstr "Passwort ändern" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:6 +#: .\contrib\admin\templates\registration\password_change_done.html.py:10 +msgid "Password change successful" +msgstr "Passwort erfolgreich geändert" + +#: .\contrib\admin\templates\registration\password_change_done.html.py:12 +msgid "Your password was changed." +msgstr "Ihr Passwort wurde geändert." + +#: .\contrib\admin\templates\registration\password_change_form.html.py:12 +msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." +msgstr "Bitte geben Sie aus Sicherheitsgründen erst Ihr altes Passwort und darunter dann zweimal (um sicherzustellen, dass Sie es korrekt eingegeben haben) das neue Kennwort ein." + +#: .\contrib\admin\templates\registration\password_change_form.html.py:17 +msgid "Old password:" +msgstr "Altes Passwort:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:19 +msgid "New password:" +msgstr "Neues Passwort:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:21 +msgid "Confirm password:" +msgstr "Passwort wiederholen:" + +#: .\contrib\admin\templates\registration\password_change_form.html.py:23 +msgid "Change my password" +msgstr "Mein Passwort ändern" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:4 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_form.html.py:10 +msgid "Password reset" +msgstr "Passwort zurücksetzen" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:6 +#: .\contrib\admin\templates\registration\password_reset_done.html.py:10 +msgid "Password reset successful" +msgstr "Passwort wurde erfolgreich zurückgesetzt" + +#: .\contrib\admin\templates\registration\password_reset_done.html.py:12 +msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." +msgstr "Wir haben ein neues Passwort an die von Ihnen angegebene E-Mail-Adresse geschickt. Sie sollten es in Kürze erhalten." + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Sie erhalten diese E-Mail, weil Sie ein neues Passwort" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "für Ihren Benutzer bei %(site_name)s angefordert haben." + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Ihr neues Passwort lautet: %(new_password)s" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Sie können das Passwort auf folgender Seite ändern:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:11 +msgid "Your username, in case you've forgotten:" +msgstr "Ihr Benutzername, falls Sie ihn vergessen haben:" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:13 +msgid "Thanks for using our site!" +msgstr "Vielen Dank, dass Sie unsere Seiten benutzen!" + +#: .\contrib\admin\templates\registration\password_reset_email.html.py:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Das Team von %(site_name)s" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:12 +msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." +msgstr "Passwort vergessen? Einfach die E-Mail-Adresse eingeben und wir setzen das Passwort zurück und lassen es Ihnen per E-Mail zukommen." + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:16 +msgid "E-mail address:" +msgstr "E-Mail-Adresse:" + +#: .\contrib\admin\templates\registration\password_reset_form.html.py:16 +msgid "Reset my password" +msgstr "Mein Passwort zurücksetzen" + +#: .\contrib\admin\templates\widget\date_time.html.py:3 +msgid "Date:" +msgstr "Datum:" + +#: .\contrib\admin\templates\widget\date_time.html.py:4 +msgid "Time:" +msgstr "Zeit:" + +#: .\contrib\admin\templates\widget\file.html.py:2 +msgid "Currently:" +msgstr "Derzeit:" + +#: .\contrib\admin\templates\widget\file.html.py:3 +msgid "Change:" +msgstr "Ändern:" + +#: .\contrib\admin\templatetags\admin_list.py:238 +msgid "All dates" +msgstr "Alle Tage" + +#: .\contrib\admin\views\auth.py:19 +#: .\contrib\admin\views\main.py:257 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" wurde erfolgreich hinzugefügt." + +#: .\contrib\admin\views\auth.py:24 +#: .\contrib\admin\views\main.py:261 +#: .\contrib\admin\views\main.py:347 +msgid "You may edit it again below." +msgstr "Das Element kann jetzt weiter bearbeitet werden." + +#: .\contrib\admin\views\auth.py:30 +msgid "Add user" +msgstr "Benutzer hinzufügen" + +#: .\contrib\admin\views\auth.py:57 +msgid "Password changed successfully." +msgstr "Passwort erfolgreich geändert." + +#: .\contrib\admin\views\auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "Passwort ändern: %s" + +#: .\contrib\admin\views\decorators.py:10 +#: .\contrib\auth\forms.py:59 +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +msgstr "Bitte einen Benutzernamen und ein Passwort eingeben. Beide Felder berücksichtigen die Groß-/Kleinschreibung." + +#: .\contrib\admin\views\decorators.py:62 +msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." +msgstr "Bitte neu anmelden, da die Session ausgelaufen ist. Keine Angst, die Beiträge wurden gesichert." + +#: .\contrib\admin\views\decorators.py:69 +msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." +msgstr "Es sieht danach aus, dass der Browser keine Cookies akzeptiert. Bitte im Browser Cookies aktivieren und diese Seite neu laden." + +#: .\contrib\admin\views\decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Benutzernamen dürfen das Zeichen '@' nicht enthalten." + +#: .\contrib\admin\views\decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Die E-Mail-Adresse entspricht nicht Ihrem Benutzernamen. Bitte stattdessen '%s' versuchen." + +#: .\contrib\admin\views\doc.py:46 +#: .\contrib\admin\views\doc.py:48 +#: .\contrib\admin\views\doc.py:50 +msgid "tag:" +msgstr "Schlagwort:" + +#: .\contrib\admin\views\doc.py:77 +#: .\contrib\admin\views\doc.py:79 +#: .\contrib\admin\views\doc.py:81 +msgid "filter:" +msgstr "Filter:" + +#: .\contrib\admin\views\doc.py:135 +#: .\contrib\admin\views\doc.py:137 +#: .\contrib\admin\views\doc.py:139 +msgid "view:" +msgstr "Ansicht:" + +#: .\contrib\admin\views\doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "Anwendung %r nicht gefunden" + +#: .\contrib\admin\views\doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Modell %r wurde nicht in Anwendung %r gefunden" + +#: .\contrib\admin\views\doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "Das verknüpfte `%s.%s` Objekt" + +#: .\contrib\admin\views\doc.py:183 +#: .\contrib\admin\views\doc.py:205 +#: .\contrib\admin\views\doc.py:219 +#: .\contrib\admin\views\doc.py:224 +msgid "model:" +msgstr "Modell:" + +#: .\contrib\admin\views\doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "verknüpftes `%s.%s` Objekt" + +#: .\contrib\admin\views\doc.py:219 +#, python-format +msgid "all %s" +msgstr "Alle %s" + +#: .\contrib\admin\views\doc.py:224 +#, python-format +msgid "number of %s" +msgstr "Anzahl von %s" + +#: .\contrib\admin\views\doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Felder am %s Objekt" + +#: .\contrib\admin\views\doc.py:291 +#: .\contrib\admin\views\doc.py:301 +#: .\contrib\admin\views\doc.py:303 +#: .\contrib\admin\views\doc.py:309 +#: .\contrib\admin\views\doc.py:310 +#: .\contrib\admin\views\doc.py:312 +msgid "Integer" +msgstr "Ganzzahl" + +#: .\contrib\admin\views\doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Boolscher Wert (True oder False)" + +#: .\contrib\admin\views\doc.py:293 +#: .\contrib\admin\views\doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Zeichenkette (bis zu %(maxlength)s Zeichen)" + +#: .\contrib\admin\views\doc.py:294 +msgid "Comma-separated integers" +msgstr "Kommaseparierte Liste von Ganzzahlen" + +#: .\contrib\admin\views\doc.py:295 +msgid "Date (without time)" +msgstr "Datum (ohne Uhrzeit)" + +#: .\contrib\admin\views\doc.py:296 +msgid "Date (with time)" +msgstr "Datum (mit Uhrzeit)" + +#: .\contrib\admin\views\doc.py:297 +msgid "E-mail address" +msgstr "E-Mail-Adresse" + +#: .\contrib\admin\views\doc.py:298 +#: .\contrib\admin\views\doc.py:299 +#: .\contrib\admin\views\doc.py:302 +msgid "File path" +msgstr "Dateipfad" + +#: .\contrib\admin\views\doc.py:300 +msgid "Decimal number" +msgstr "Dezimalzahl" + +#: .\contrib\admin\views\doc.py:304 +#: .\contrib\comments\models.py:85 +msgid "IP address" +msgstr "IP-Adresse" + +#: .\contrib\admin\views\doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Boolscher Wert (True, False oder None)" + +#: .\contrib\admin\views\doc.py:307 +msgid "Relation to parent model" +msgstr "Beziehung zum Eltern-Modell" + +#: .\contrib\admin\views\doc.py:308 +msgid "Phone number" +msgstr "Telefonnummer" + +#: .\contrib\admin\views\doc.py:313 +msgid "Text" +msgstr "Text" + +#: .\contrib\admin\views\doc.py:314 +msgid "Time" +msgstr "Zeit" + +#: .\contrib\admin\views\doc.py:315 +#: .\contrib\flatpages\models.py:7 +msgid "URL" +msgstr "Adresse (URL)" + +#: .\contrib\admin\views\doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "U.S. Bundesstaat (zwei Großbuchstaben)" + +#: .\contrib\admin\views\doc.py:317 +msgid "XML text" +msgstr "XML-Text" + +#: .\contrib\admin\views\doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s ist scheinbar kein urlpattern Objekt" + +#: .\contrib\admin\views\main.py:223 +msgid "Site administration" +msgstr "Website Verwaltung" + +#: .\contrib\admin\views\main.py:271 +#: .\contrib\admin\views\main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Jetzt kann ein weiteres Element vom Typ %s angelegt werden." + +#: .\contrib\admin\views\main.py:289 +#, python-format +msgid "Add %s" +msgstr "%s hinzufügen" + +#: .\contrib\admin\views\main.py:335 +#, python-format +msgid "Added %s." +msgstr "%s hinzugefügt." + +#: .\contrib\admin\views\main.py:335 +#: .\contrib\admin\views\main.py:337 +#: .\contrib\admin\views\main.py:339 +#: .\db\models\manipulators.py:306 +msgid "and" +msgstr "und" + +#: .\contrib\admin\views\main.py:337 +#, python-format +msgid "Changed %s." +msgstr "%s geändert" + +#: .\contrib\admin\views\main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "%s gelöscht." + +#: .\contrib\admin\views\main.py:342 +msgid "No fields changed." +msgstr "Keine Felder geändert." + +#: .\contrib\admin\views\main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" wurde erfolgreich geändert." + +#: .\contrib\admin\views\main.py:353 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" wurde erfolgreich hinzugefügt. Das Element kann jetzt geändert werden." + +#: .\contrib\admin\views\main.py:391 +#, python-format +msgid "Change %s" +msgstr "%s ändern" + +#: .\contrib\admin\views\main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Ein oder mehrere %(fieldname)s in %(name)s: %(obj)s" + +#: .\contrib\admin\views\main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Ein oder mehrere %(fieldname)s in %(name)s:" + +#: .\contrib\admin\views\main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" wurde erfolgreich gelöscht." + +#: .\contrib\admin\views\main.py:514 +msgid "Are you sure?" +msgstr "Sind Sie ganz sicher?" + +#: .\contrib\admin\views\main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Änderungsgeschichte: %s" + +#: .\contrib\admin\views\main.py:570 +#, python-format +msgid "Select %s" +msgstr "%s auswählen" + +#: .\contrib\admin\views\main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "%s zur Änderung auswählen" + +#: .\contrib\admin\views\main.py:758 +msgid "Database error" +msgstr "Datenbankfehler" + +#: .\contrib\auth\forms.py:16 +#: .\contrib\auth\forms.py:137 +msgid "The two password fields didn't match." +msgstr "Die beiden Passwörter sind nicht identisch." + +#: .\contrib\auth\forms.py:24 +msgid "A user with that username already exists." +msgstr "Ein Benutzer mit diesem Namen existiert bereits." + +#: .\contrib\auth\forms.py:52 +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "Der Webbrowser scheint keine Cookies aktiviert zu haben. Cookies sind für die Anmeldung zwingend erforderlich." + +#: .\contrib\auth\forms.py:61 +msgid "This account is inactive." +msgstr "Dieser Benutzer ist inaktiv." + +#: .\contrib\auth\forms.py:84 +msgid "That e-mail address doesn't have an associated user account. Are you sure you've registered?" +msgstr "Zu dieser E-Mail-Adresse existiert kein Benutzer. Sicher, dass Sie sich mit dieser Adresse angemeldet haben?" + +#: .\contrib\auth\forms.py:116 +msgid "The two 'new password' fields didn't match." +msgstr "Die beiden neuen Passwörter sind nicht identisch." + +#: .\contrib\auth\forms.py:123 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "Das alte Passwort war falsch. Bitte neu eingeben." + +#: .\contrib\auth\models.py:38 +#: .\contrib\auth\models.py:57 +msgid "name" +msgstr "Name" + +#: .\contrib\auth\models.py:40 +msgid "codename" +msgstr "Codename" + +#: .\contrib\auth\models.py:42 +msgid "permission" +msgstr "Berechtigung" + +#: .\contrib\auth\models.py:43 +#: .\contrib\auth\models.py:58 +msgid "permissions" +msgstr "Berechtigungen" + +#: .\contrib\auth\models.py:60 +msgid "group" +msgstr "Gruppe" + +#: .\contrib\auth\models.py:61 +#: .\contrib\auth\models.py:100 +msgid "groups" +msgstr "Gruppen" + +#: .\contrib\auth\models.py:90 +msgid "username" +msgstr "Benutzername" + +#: .\contrib\auth\models.py:90 +msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." +msgstr "Erforderlich. 30 Zeichen oder weniger. Alphanumerische Zeichen (Buchstaben, Ziffern und Unterstriche sind erlaubt)." + +#: .\contrib\auth\models.py:91 +msgid "first name" +msgstr "Vorname" + +#: .\contrib\auth\models.py:92 +msgid "last name" +msgstr "Nachname" + +#: .\contrib\auth\models.py:93 +msgid "e-mail address" +msgstr "E-Mail-Adresse" + +#: .\contrib\auth\models.py:94 +msgid "password" +msgstr "Passwort" + +#: .\contrib\auth\models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]' or use the change password form." +msgstr "Die Form '[algo]$[salt]$[hexdigest]' verwenden, oder das Passwort ändern Formular benutzen." + +#: .\contrib\auth\models.py:95 +msgid "staff status" +msgstr "Administrator" + +#: .\contrib\auth\models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Legt fest, ob sich der Benutzer an der Administrationsseite anmelden kann." + +#: .\contrib\auth\models.py:96 +msgid "active" +msgstr "Aktiv" + +#: .\contrib\auth\models.py:96 +msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." +msgstr "Legt fest, ob sich der Benutzer an der Administrationsseite anmelden kann. Anstatt einen Benutzer zu löschen, kann er hier auch einfach deaktiviert werden." + +#: .\contrib\auth\models.py:97 +msgid "superuser status" +msgstr "Hauptadmin." + +#: .\contrib\auth\models.py:97 +msgid "Designates that this user has all permissions without explicitly assigning them." +msgstr "Legt fest, dass der Benutzer alle Berechtigungen hat, ohne diese einzeln zuweisen zu müssen." + +#: .\contrib\auth\models.py:98 +msgid "last login" +msgstr "Letzte Anmeldung" + +#: .\contrib\auth\models.py:99 +msgid "date joined" +msgstr "Mitglied seit" + +#: .\contrib\auth\models.py:101 +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "Zusätzlich zu den manuell angelegten Rechten erhält dieser Benutzer auch alle Rechte, die seine zugewiesenen Gruppen haben." + +#: .\contrib\auth\models.py:102 +msgid "user permissions" +msgstr "Berechtigungen" + +#: .\contrib\auth\models.py:105 +msgid "user" +msgstr "Benutzer" + +#: .\contrib\auth\models.py:106 +msgid "users" +msgstr "Benutzer" + +#: .\contrib\auth\models.py:111 +msgid "Personal info" +msgstr "Persönliche Infos" + +#: .\contrib\auth\models.py:112 +msgid "Permissions" +msgstr "Berechtigungen" + +#: .\contrib\auth\models.py:113 +msgid "Important dates" +msgstr "Wichtige Daten" + +#: .\contrib\auth\models.py:114 +msgid "Groups" +msgstr "Gruppen" + +#: .\contrib\auth\models.py:258 +msgid "message" +msgstr "Mitteilung" + +#: .\contrib\auth\views.py:39 +msgid "Logged out" +msgstr "Abgemeldet" + +#: .\contrib\comments\models.py:67 +#: .\contrib\comments\models.py:166 +msgid "object ID" +msgstr "Objekt-ID" + +#: .\contrib\comments\models.py:68 +msgid "headline" +msgstr "Überschrift" + +#: .\contrib\comments\models.py:69 +#: .\contrib\comments\models.py:90 +#: .\contrib\comments\models.py:167 +msgid "comment" +msgstr "Kommentar" + +#: .\contrib\comments\models.py:70 +msgid "rating #1" +msgstr "Bewertung #1" + +#: .\contrib\comments\models.py:71 +msgid "rating #2" +msgstr "Bewertung #2" + +#: .\contrib\comments\models.py:72 +msgid "rating #3" +msgstr "Bewertung #3" + +#: .\contrib\comments\models.py:73 +msgid "rating #4" +msgstr "Bewertung #4" + +#: .\contrib\comments\models.py:74 +msgid "rating #5" +msgstr "Bewertung #5" + +#: .\contrib\comments\models.py:75 +msgid "rating #6" +msgstr "Bewertung #6" + +#: .\contrib\comments\models.py:76 +msgid "rating #7" +msgstr "Bewertung #7" + +#: .\contrib\comments\models.py:77 +msgid "rating #8" +msgstr "Bewertung #8" + +#: .\contrib\comments\models.py:82 +msgid "is valid rating" +msgstr "ist eine Bewertung" + +#: .\contrib\comments\models.py:83 +#: .\contrib\comments\models.py:169 +msgid "date/time submitted" +msgstr "Datum/Zeit Erstellung" + +#: .\contrib\comments\models.py:84 +#: .\contrib\comments\models.py:170 +msgid "is public" +msgstr "ist öffentlich" + +#: .\contrib\comments\models.py:86 +msgid "is removed" +msgstr "ist gelöscht" + +#: .\contrib\comments\models.py:86 +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "Hier einen Haken setzen, wenn der Kommentar unpassend ist. Stattdessen wird dann \"Dieser Kommentar wurde entfernt\" Meldung angezeigt." + +#: .\contrib\comments\models.py:91 +msgid "comments" +msgstr "Kommentare" + +#: .\contrib\comments\models.py:131 +#: .\contrib\comments\models.py:207 +msgid "Content object" +msgstr "Inhaltsobjekt" + +#: .\contrib\comments\models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Geschrieben von %(user)s am %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: .\contrib\comments\models.py:168 +msgid "person's name" +msgstr "Autorname" + +#: .\contrib\comments\models.py:171 +msgid "ip address" +msgstr "IP-Adresse" + +#: .\contrib\comments\models.py:173 +msgid "approved by staff" +msgstr "Bestätigt vom Betreiber" + +#: .\contrib\comments\models.py:176 +msgid "free comment" +msgstr "Freier Kommentar" + +#: .\contrib\comments\models.py:177 +msgid "free comments" +msgstr "Freie Kommentare" + +#: .\contrib\comments\models.py:233 +msgid "score" +msgstr "Bewertung" + +#: .\contrib\comments\models.py:234 +msgid "score date" +msgstr "Bewertungsdatum" + +#: .\contrib\comments\models.py:237 +msgid "karma score" +msgstr "Karma Bewertung" + +#: .\contrib\comments\models.py:238 +msgid "karma scores" +msgstr "Karma Bewertungen" + +#: .\contrib\comments\models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d Bewertung von %(user)s" + +#: .\contrib\comments\models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Dieser Kommentar ist von %(user)s markiert:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\models.py:265 +msgid "flag date" +msgstr "Kennzeichnungsdatum" + +#: .\contrib\comments\models.py:268 +msgid "user flag" +msgstr "Benutzerkennzeichnung" + +#: .\contrib\comments\models.py:269 +msgid "user flags" +msgstr "Benutzerkennzeichnungen" + +#: .\contrib\comments\models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Gekennzeichnet von %r" + +#: .\contrib\comments\models.py:278 +msgid "deletion date" +msgstr "Löschdatum" + +#: .\contrib\comments\models.py:280 +msgid "moderator deletion" +msgstr "Löschung vom Moderator" + +#: .\contrib\comments\models.py:281 +msgid "moderator deletions" +msgstr "Löschungen vom Moderator" + +#: .\contrib\comments\models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Vom Moderator %r gelöscht" + +#: .\contrib\comments\templates\comments\form.html.py:8 +msgid "Forgotten your password?" +msgstr "Passwort vergessen?" + +#: .\contrib\comments\templates\comments\form.html.py:12 +msgid "Ratings" +msgstr "Bewertungen" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Required" +msgstr "Erforderlich" + +#: .\contrib\comments\templates\comments\form.html.py:12 +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Optional" +msgstr "Optional" + +#: .\contrib\comments\templates\comments\form.html.py:23 +msgid "Post a photo" +msgstr "Ein Bild veröffentlichen" + +#: .\contrib\comments\templates\comments\form.html.py:28 +#: .\contrib\comments\templates\comments\freeform.html.py:5 +msgid "Comment:" +msgstr "Kommentar:" + +#: .\contrib\comments\templates\comments\form.html.py:35 +#: .\contrib\comments\templates\comments\freeform.html.py:10 +msgid "Preview comment" +msgstr "Kommentarvorschau" + +#: .\contrib\comments\templates\comments\freeform.html.py:4 +msgid "Your name:" +msgstr "Ihr Name:" + +#: .\contrib\comments\views\comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "Diese Abstimmung ist zwingend erforderlich, da Du an mindestens einer weiteren Abstimmung teilnimmst." + +#: .\contrib\comments\views\comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Dieser Kommentar ist von einem Benutzer mit weniger als %(count)s Kommentar:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Dieser Kommentar ist von einem Benutzer mit weniger als %(count)s Kommentaren:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Dieser Kommentar ist von einem nicht einschätzbaren Benutzer:\n" +"\n" +"%(text)s" + +#: .\contrib\comments\views\comments.py:188 +#: .\contrib\comments\views\comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Nur POST ist erlaubt" + +#: .\contrib\comments\views\comments.py:192 +#: .\contrib\comments\views\comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Eines oder mehrere der erforderlichen Felder fehlen" + +#: .\contrib\comments\views\comments.py:196 +#: .\contrib\comments\views\comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Jemand hat mit dem Kommentarformular herumgespielt (Sicherheitsverletzung)" + +#: .\contrib\comments\views\comments.py:206 +#: .\contrib\comments\views\comments.py:292 +msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgstr "Das Kommentarformular hatte einen falschen 'target' Parameter -- die Objekt-ID ist ungültig." + +#: .\contrib\comments\views\comments.py:257 +#: .\contrib\comments\views\comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Das Kommentarformular wurde nicht mit 'preview' oder 'post' abgeschickt" + +#: .\contrib\comments\views\karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonyme Benutzer dürfen nicht abstimmen" + +#: .\contrib\comments\views\karma.py:23 +msgid "Invalid comment ID" +msgstr "Ungültige Kommentar-ID" + +#: .\contrib\comments\views\karma.py:25 +msgid "No voting for yourself" +msgstr "Keine Abstimmung für dich selbst" + +#: .\contrib\contenttypes\models.py:26 +msgid "python model class name" +msgstr "Python Model-Klassenname" + +#: .\contrib\contenttypes\models.py:29 +msgid "content type" +msgstr "Inhaltstyp" + +#: .\contrib\contenttypes\models.py:30 +msgid "content types" +msgstr "Inhaltstypen" + +#: .\contrib\flatpages\models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Beispiel: '/about/contact/'. Wichtig: vorne und hinten muss ein / stehen." + +#: .\contrib\flatpages\models.py:9 +msgid "title" +msgstr "Titel" + +#: .\contrib\flatpages\models.py:10 +msgid "content" +msgstr "Inhalt" + +#: .\contrib\flatpages\models.py:11 +msgid "enable comments" +msgstr "Kommentare aktivieren" + +#: .\contrib\flatpages\models.py:12 +msgid "template name" +msgstr "Name der Vorlage" + +#: .\contrib\flatpages\models.py:13 +msgid "Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'." +msgstr "Beispiel: 'flatpages/contact_page.html'. Wenn dieses Feld nicht gefüllt ist, wird 'flatpages/default.html' als Standard gewählt." + +#: .\contrib\flatpages\models.py:14 +msgid "registration required" +msgstr "Registrierung erforderlich" + +#: .\contrib\flatpages\models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Wenn hier ein Haken gesetzt ist, können nur angemeldete Benutzer diese Seite sehen." + +#: .\contrib\flatpages\models.py:18 +msgid "flat page" +msgstr "Webseite" + +#: .\contrib\flatpages\models.py:19 +msgid "flat pages" +msgstr "Webseiten" + +#: .\contrib\redirects\models.py:7 +msgid "redirect from" +msgstr "Umleitung von" + +#: .\contrib\redirects\models.py:8 +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "Hier sollte ein absoluter Pfad stehen, ohne den Domainnamen. Beispiel: '/events/search/'." + +#: .\contrib\redirects\models.py:9 +msgid "redirect to" +msgstr "Umleitung zu" + +#: .\contrib\redirects\models.py:10 +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "Hier muss entweder ein absoluter Pfad oder eine komplette URL mit http:// am Anfang stehen." + +#: .\contrib\redirects\models.py:13 +msgid "redirect" +msgstr "Umleitung" + +#: .\contrib\redirects\models.py:14 +msgid "redirects" +msgstr "Umleitungen" + +#: .\contrib\sessions\models.py:51 +msgid "session key" +msgstr "Sitzungs-ID" + +#: .\contrib\sessions\models.py:52 +msgid "session data" +msgstr "Sitzungsdaten" + +#: .\contrib\sessions\models.py:53 +msgid "expire date" +msgstr "Verfallsdatum" + +#: .\contrib\sessions\models.py:57 +msgid "session" +msgstr "Sitzung" + +#: .\contrib\sessions\models.py:58 +msgid "sessions" +msgstr "Sitzungen" + +#: .\contrib\sites\models.py:10 +msgid "domain name" +msgstr "Domainname" + +#: .\contrib\sites\models.py:11 +msgid "display name" +msgstr "Anzeigename" + +#: .\contrib\sites\models.py:15 +msgid "site" +msgstr "Website" + +#: .\contrib\sites\models.py:16 +msgid "sites" +msgstr "Websites" + +#: .\core\validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Dieser Wert darf nur Buchstaben, Ziffern und Unterstriche enthalten." + +#: .\core\validators.py:68 +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "Dieser Wert darf nur Buchstaben, Ziffern, Unterstriche und Schrägstriche enthalten." + +#: .\core\validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Dieser Wert darf nur Buchstaben, Ziffern, Unterstriche und Bindestriche enthalten." + +#: .\core\validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Großbuchstaben sind hier nicht erlaubt." + +#: .\core\validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Kleinbuchstaben sind hier nicht erlaubt." + +#: .\core\validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Hier sind nur durch Komma getrennte Ziffern erlaubt." + +#: .\core\validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Bitte mit Komma getrennte, gültige E-Mail-Adressen eingeben." + +#: .\core\validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Bitte eine gültige IP-Adresse eingeben." + +#: .\core\validators.py:107 +msgid "Empty values are not allowed here." +msgstr "Dieses Feld darf nicht leer sein." + +#: .\core\validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Nichtnumerische Zeichen sind hier nicht erlaubt." + +#: .\core\validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Dieser Wert darf nicht nur aus Ziffern bestehen." + +#: .\core\validators.py:120 +#: .\newforms\fields.py:126 +msgid "Enter a whole number." +msgstr "Bitte eine ganze Zahl eingeben." + +#: .\core\validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Nur alphabetische Zeichen sind hier erlaubt." + +#: .\core\validators.py:139 +msgid "Year must be 1900 or later." +msgstr "Das Jahr muss 1900 oder später sein." + +#: .\core\validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Ungültiges Datum: %s" + +#: .\core\validators.py:147 +#: .\db\models\fields\__init__.py:448 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Bitte ein gültiges Datum im Format JJJJ-MM-TT eingeben." + +#: .\core\validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Bitte eine gültige Zeit im Format SS:MM eingeben." + +#: .\core\validators.py:156 +#: .\db\models\fields\__init__.py:515 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Bitte eine gültige Datums- und Zeitangabe im Format JJJJ-MM-TT SS:MM eingeben." + +#: .\core\validators.py:161 +#: .\newforms\fields.py:269 +msgid "Enter a valid e-mail address." +msgstr "Bitte eine gültige E-Mail-Adresse eingeben." + +#: .\core\validators.py:173 +#: .\core\validators.py:442 +#: .\oldforms\__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Es wurde keine Datei übermittelt. Eventuell ist das Formular-Encoding falsch." + +#: .\core\validators.py:177 +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "Bitte ein Bild hochladen. Die hochgeladene Datei ist kein Bild, oder ist defekt." + +#: .\core\validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Die URL %s zeigt nicht auf ein gültiges Bild." + +#: .\core\validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Telefonnummern müssen das Format XXX-XXX-XXXX haben. \"%s\" ist ungültig." + +#: .\core\validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Die URL %s zeigt nicht auf ein gültiges QuickTime-Video." + +#: .\core\validators.py:200 +msgid "A valid URL is required." +msgstr "Eine gültige URL wird hier verlangt." + +#: .\core\validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Bitte gültiges HTML eingeben. Fehler sind:\n" +"%s" + +#: .\core\validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Ungültiges XML: %s" + +#: .\core\validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ungültige URL: %s" + +#: .\core\validators.py:243 +#: .\core\validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Die URL %s funktioniert nicht." + +#: .\core\validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Bitte eine gültige Abkürzung für einen US-Staat eingeben." + +#: .\core\validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Keine Schimpfworte! Das Wort %s ist hier nicht gern gesehen!" +msgstr[1] "Keine Schimpfworte! Die Wörter %s sind hier nicht gern gesehen!" + +#: .\core\validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Dieses Feld muss zum Feld '%s' passen." + +#: .\core\validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Bitte mindestens eines der Felder ausfüllen." + +#: .\core\validators.py:300 +#: .\core\validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Bitte entweder beide Felder ausfüllen, oder beide leer lassen." + +#: .\core\validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Dieses Feld muss gefüllt sein, wenn Feld %(field)s den Wert %(value)s hat." + +#: .\core\validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Dieses Feld muss gefüllt sein, wenn Feld %(field)s nicht %(value)s ist." + +#: .\core\validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "Doppelte Werte sind hier nicht erlaubt." + +#: .\core\validators.py:364 +#, python-format +msgid "This value must be between %s and %s." +msgstr "Dieser Wert muss zwischen %s und %s sein." + +#: .\core\validators.py:366 +#, python-format +msgid "This value must be at least %s." +msgstr "Dieser Wert muss mindestens %s sein." + +#: .\core\validators.py:368 +#, python-format +msgid "This value must be no more than %s." +msgstr "Dieser Wert darf maximal %s sein." + +#: .\core\validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "Dieser Wert muss eine Potenz von %s sein." + +#: .\core\validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "Bitte eine gültige Dezimalzahl eingeben." + +#: .\core\validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffer eingeben." +msgstr[1] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffern eingeben." + +#: .\core\validators.py:422 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Bitte eine gültige Dezimalzahl mit einer Gesamtzahl von maximal %s Ziffer eingeben." +msgstr[1] "Bitte eine gültige Dezimalzahl mit einer Gesamtzahl von maximal %s Ziffern eingeben." + +#: .\core\validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Bitte eine gültige Dezimalzahl mit maximal %s Dezimalstelle eingeben." +msgstr[1] "Bitte eine gültige Dezimalzahl mit maximal %s Dezimalstellen eingeben." + +#: .\core\validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Bitte sicherstellen, dass die hochgeladene Datei mindestens %s Bytes groß ist." + +#: .\core\validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Bitte sicherstellen, dass die hochgeladene Datei maximal %s Bytes groß ist." + +#: .\core\validators.py:453 +msgid "The format for this field is wrong." +msgstr "Das Format für dieses Feld ist falsch." + +#: .\core\validators.py:468 +msgid "This field is invalid." +msgstr "Dieses Feld ist ungültig." + +#: .\core\validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Konnte nichts von %s empfangen." + +#: .\core\validators.py:507 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "Die URL %(url)s lieferte den falschen Content-Type '%(contenttype)s'." + +#: .\core\validators.py:540 +#, python-format +msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgstr "Bitte das ungeschlossene %(tag)s Tag in Zeile %(line)s schließen. Die Zeile beginnt mit \"%(start)s\"." + +#: .\core\validators.py:544 +#, python-format +msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" +msgstr "In Zeile %(line)s ist Text, der nicht in dem Kontext erlaubt ist. Die Zeile beginnt mit \"%(start)s\"." + +#: .\core\validators.py:549 +#, python-format +msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" +msgstr "Das Attribute %(attr)s in Zeile %(line)s ist ungültig. Die Zeile beginnt mit \"%(start)s\"." + +#: .\core\validators.py:554 +#, python-format +msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" +msgstr "<%(tag)s> in Zeile %(line)s ist ungültig. Die Zeile beginnt mit \"%(start)s\"." + +#: .\core\validators.py:558 +#, python-format +msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" +msgstr "Ein Tag in Zeile %(line)s hat eines oder mehrere Pflichtattribute nicht. Die Zeile beginnt mit \"%(start)s\"." + +#: .\core\validators.py:563 +#, python-format +msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" +msgstr "Das Attribut %(attr)s in Zeile %(line)s hat einen ungültigen Wert. Die Zeile beginnt mit \"%(start)s\"." + +#: .\db\models\manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "Ein '%(object)s' in dieser '%(type)s' existiert bereits für dieses '%(field)s'." + +#: .\db\models\fields\related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Bitte ein gültiges '%s' eingeben." + +#: .\db\models\fields\related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Mehrere IDs können mit Komma getrennt werden." + +#: .\db\models\fields\related.py:644 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Um mehr als eine Selektion zu treffen, \"Strg\", oder auf dem Mac \"Command\", beim Klicken gedrückt halten." + +#: .\db\models\fields\related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Bitte gültige IDs für %(self)s eingeben. Der Wert %(value)r ist ungültig." +msgstr[1] "Bitte gültige IDs für %(self)s eingeben. Die Werte %(value)r sind ungültig." + +#: .\db\models\fields\__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "Ein '%(optname)s' mit diesem '%(fieldname)s' existiert bereits." + +#: .\db\models\fields\__init__.py:116 +#: .\db\models\fields\__init__.py:267 +#: .\db\models\fields\__init__.py:599 +#: .\db\models\fields\__init__.py:610 +#: .\newforms\fields.py:78 +#: .\newforms\fields.py:373 +#: .\newforms\fields.py:449 +#: .\newforms\fields.py:460 +#: .\oldforms\__init__.py:352 +msgid "This field is required." +msgstr "Dieses Feld ist zwingend erforderlich." + +#: .\db\models\fields\__init__.py:360 +msgid "This value must be an integer." +msgstr "Dieser Wert muss eine Ganzzahl sein." + +#: .\db\models\fields\__init__.py:395 +msgid "This value must be either True or False." +msgstr "Dieser Wert muss wahr oder falsch sein." + +#: .\db\models\fields\__init__.py:416 +msgid "This field cannot be null." +msgstr "Dieses Feld darf nicht leer sein." + +#: .\db\models\fields\__init__.py:619 +msgid "Enter a valid filename." +msgstr "Bitte einen gültigen Dateinamen eingeben." + +#: .\newforms\fields.py:101 +#: .\newforms\fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Bitte sicherstellen, dass der Text maximal %d Zeichen hat." + +#: .\newforms\fields.py:103 +#: .\newforms\fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Bitte sicherstellen, dass der Text wenigstens %d Zeichen hat." + +#: .\newforms\fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Dieser Wert darf maximal %s sein." + +#: .\newforms\fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Dieser Wert muss größer oder gleich %s sein." + +#: .\newforms\fields.py:163 +msgid "Enter a valid date." +msgstr "Bitte ein gültiges Datum eingeben." + +#: .\newforms\fields.py:190 +msgid "Enter a valid time." +msgstr "Bitte eine gültige Uhrzeit eingeben." + +#: .\newforms\fields.py:226 +msgid "Enter a valid date/time." +msgstr "Bitte gültiges Datum und Uhrzeit eingeben." + +#: .\newforms\fields.py:240 +msgid "Enter a valid value." +msgstr "Bitte einen gültigen Wert eingeben." + +#: .\newforms\fields.py:287 +#: .\newforms\fields.py:309 +msgid "Enter a valid URL." +msgstr "Bitte eine gültige Adresse eingeben." + +#: .\newforms\fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "Diese Adresse scheint nicht gültig zu sein." + +#: .\newforms\fields.py:359 +#: .\newforms\fields.py:386 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Bitte eine gültige Auswahl treffen. %s ist keine gültige Auswahl." + +#: .\newforms\fields.py:377 +#: .\newforms\fields.py:453 +msgid "Enter a list of values." +msgstr "Eine Liste mit Werten eingeben." + +#: .\oldforms\__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Bitte sicherstellen, dass der Text weniger als %s Zeichen hat." +msgstr[1] "Bitte sicherstellen, dass der Text weniger als %s Zeichen hat." + +#: .\oldforms\__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "Zeilenumbrüche sind hier nicht erlaubt." + +#: .\oldforms\__init__.py:493 +#: .\oldforms\__init__.py:566 +#: .\oldforms\__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Bitte eine gültige Auswahl treffen; '%(data)s' ist nicht in %(choices)s." + +#: .\oldforms\__init__.py:669 +msgid "The submitted file is empty." +msgstr "Die ausgewählte Datei ist leer." + +#: .\oldforms\__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Bitte eine Ganzzahl zwischen -32.768 und 32.767 eingeben." + +#: .\oldforms\__init__.py:735 +msgid "Enter a positive number." +msgstr "Bitte eine ganze, positive Zahl eingeben." + +#: .\oldforms\__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Bitte eine ganze Zahl zwischen 0 und 32.767 eingeben." + +#: .\template\defaultfilters.py:419 +msgid "yes,no,maybe" +msgstr "Ja,Nein,Vielleicht" + +#: .\utils\dates.py:6 +msgid "Monday" +msgstr "Montag" + +#: .\utils\dates.py:6 +msgid "Tuesday" +msgstr "Dienstag" + +#: .\utils\dates.py:6 +msgid "Wednesday" +msgstr "Mittwoch" + +#: .\utils\dates.py:6 +msgid "Thursday" +msgstr "Donnerstag" + +#: .\utils\dates.py:6 +msgid "Friday" +msgstr "Freitag" + +#: .\utils\dates.py:7 +msgid "Saturday" +msgstr "Samstag" + +#: .\utils\dates.py:7 +msgid "Sunday" +msgstr "Sonntag" + +#: .\utils\dates.py:14 +msgid "January" +msgstr "Januar" + +#: .\utils\dates.py:14 +msgid "February" +msgstr "Februar" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "March" +msgstr "März" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "April" +msgstr "April" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "May" +msgstr "Mai" + +#: .\utils\dates.py:14 +#: .\utils\dates.py:27 +msgid "June" +msgstr "Juni" + +#: .\utils\dates.py:15 +#: .\utils\dates.py:27 +msgid "July" +msgstr "Juli" + +#: .\utils\dates.py:15 +msgid "August" +msgstr "August" + +#: .\utils\dates.py:15 +msgid "September" +msgstr "September" + +#: .\utils\dates.py:15 +msgid "October" +msgstr "Oktober" + +#: .\utils\dates.py:15 +msgid "November" +msgstr "November" + +#: .\utils\dates.py:16 +msgid "December" +msgstr "Dezember" + +#: .\utils\dates.py:19 +msgid "jan" +msgstr "Jan" + +#: .\utils\dates.py:19 +msgid "feb" +msgstr "Feb" + +#: .\utils\dates.py:19 +msgid "mar" +msgstr "Mär" + +#: .\utils\dates.py:19 +msgid "apr" +msgstr "Apr" + +#: .\utils\dates.py:19 +msgid "may" +msgstr "Mai" + +#: .\utils\dates.py:19 +msgid "jun" +msgstr "Jun" + +#: .\utils\dates.py:20 +msgid "jul" +msgstr "Jul" + +#: .\utils\dates.py:20 +msgid "aug" +msgstr "Aug" + +#: .\utils\dates.py:20 +msgid "sep" +msgstr "Sep" + +#: .\utils\dates.py:20 +msgid "oct" +msgstr "Okt" + +#: .\utils\dates.py:20 +msgid "nov" +msgstr "Nov" + +#: .\utils\dates.py:20 +msgid "dec" +msgstr "Dez" + +#: .\utils\dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: .\utils\dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: .\utils\dates.py:28 +msgid "Aug." +msgstr "Aug." + +#: .\utils\dates.py:28 +msgid "Sept." +msgstr "Sept." + +#: .\utils\dates.py:28 +msgid "Oct." +msgstr "Okt." + +#: .\utils\dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: .\utils\dates.py:28 +msgid "Dec." +msgstr "Dez." + +#: .\utils\timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "Jahr" +msgstr[1] "Jahre" + +#: .\utils\timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "Monat" +msgstr[1] "Monate" + +#: .\utils\timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "Woche" +msgstr[1] "Wochen" + +#: .\utils\timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "Tag" +msgstr[1] "Tage" + +#: .\utils\timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "Stunde" +msgstr[1] "Stunden" + +#: .\utils\timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "Minute" +msgstr[1] "Minuten" + +#: .\utils\translation\trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j. N Y" + +#: .\utils\translation\trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j. N Y, H:i" + +#: .\utils\translation\trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: .\utils\translation\trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: .\utils\translation\trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "j. F" + +#: .\views\generic\create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s wurde erfolgreich angelegt." + +#: .\views\generic\create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s wurde erfolgreich aktualisiert." + +#: .\views\generic\create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(verbose_name)s wurde gelöscht" + diff --git a/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/de/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..9f39c16444ba716738690d4f46f7261636da4712 GIT binary patch literal 1569 zcwTLjL5v$U6ow5+mXekN3k516VF?w1(4@)IMQK_PVYA!rLfR@xsd{u~-p;r?u_N1) zG!Z8bm5`7S2M$QwIdS67l?w+h+z?zUapi&#QV;wdPg|+flIMH2pWl17|M`8h{xw5; z1^rd@-_XyXfBOV0Tl-1IHoy*81H0e}@FI8&+^^P0;4|P&unDH%)1ayNDcAt-fOYT- z@H+Ta73a@t{;w*}-&NdytGIrW11D?2?=08_&(%WQSuMn|wJIO@2F`G&TK~Qt>iVG` z;{8|;{y*1)|F7Uv;2+?#z8>%-_)o=+MyTslBlw>NN8sz=ad6QH^?ud}`981wzHEei z-+-@y-!($spTG;?FO~m^jj(@nBh+yk^iNQ(vCv**;SBz*d`$0utXlImzQn@bN8722 z&!N9i+5Qe+X5mbm)f}$6Jp1$2*w=rYJ<{IgqBU!=ev+{MLdaZ9bK++#+YvdbL>L$< zrkw3$T3h0RJ2|5Vqr6v@9`6|6~Cn9|rY z(qk-KVPLR{4&RwJ261kZbyO}~Gx`=1NBc?i2^lN3>hZxXp;FBc#6pZ?Bj=8HTJ7eD z=GwSuXj7R)9~P;NCc4L)*RD>Y9YexlYcvp!dVH(h-i|t3QTsgabb8yJx7(d|8;;S4 z7ScY(?;P*E)7z^2J{HzR6C;$(h0_M*NOQz@ZJgz}*NX{8EefL#(%J2nBKLlCxIf%| zcmQASj#iEoBCX~~<>*ZmpB&fs<>>J8O2km4w?)bh z$gLgQRSpMN$m)eTD7zQtC0gE7xNek-(w8F3xifTlh#v`J, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django JavaScript 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2005-12-04 13:21+0100\n" +"Last-Translator: Dirk Eschler \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Verfügbare %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Alles auswählen" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Hinzufügen" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Entfernen" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Ausgewählte %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Gewünschte Auswahl treffen und " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Alles abwählen" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Januar Februar März April Mai Juni Juli August September Oktober November " +"Dezember" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "S M D M D F S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Jetzt" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Uhr" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Uhrzeit" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Mitternacht" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 Uhr" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Mittag" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Abbrechen" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Heute" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalender" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Gestern" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Morgen" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Anzeigen" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Verbergen" + diff --git a/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..4a7d8e41f0dc284ebbe17c274f756adb542238c8 GIT binary patch literal 15668 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.po new file mode 100644 index 0000000..06099eb --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/django.po @@ -0,0 +1,1921 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2006 and beyond +# This file is distributed under the same license as the PACKAGE package. +# Panos Laganakos , Mar 2006. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:13+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: panos laganakos \n" +"Language-Team: Greek\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID αντικειμένου" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "Επικεφαλίδα" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "σχόλιο" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "κατάταξη #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "βαθμολογία #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "βαθμολογία #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "βαθμολογία #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "βαθμολογία #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "βαθμολογία #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "βαθμολογία #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "βαθμολογία #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "είναι έγκυρη βαθμολογία" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "ημερομηνία/ώρα υποβολής" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "είναι δημόσιο" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "IP διεύθυνση" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "είναι διεγραμμένο" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Σημειώστε αυτό το κουτί εάν το σχόλιο είναι ανάρμοστο. Ένα Αυτό το σχόλιο " +"εσβήσθει\" μήνυμα θα εμφανιστεί αντί αυτού." + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "σχόλιο" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Σχόλιο από τον/την %(user)s την %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "όνομα ατόμου" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "ip διεύθυνση" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "εγκεκριμένο από το προσωπικό" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "Ελεύθερο σχόλιο" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "Ελεύθερα σχόλια" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "βαθμολογία" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "ημερομηνία βαθμολογίας" + +#: contrib/comments/models.py:237 +#, fuzzy +msgid "karma score" +msgstr "karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Αυτο το σχόλιο σημειώθηκε απο %(χρήστη)ες\n" +"\n" +"%(κείμενο)α" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "ημερομηνία διαγραφής" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Ανώνυμοι χρήστες δέν μπορούν να ψηφήσουν" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Αυτή η βαθμολογία απαιτείται επειδή τουλάχιστον ακόμα μια βαθμολογία" + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Ένα ή περισσότερα από τα απαιτούμενα πεδία δεν υπεβλήθει" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Όνομα χρήστη:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Κωδικός" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Ξεχάσατε τον κωδικό σας;" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Αποσύνδεση" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Βαθμολογίες" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Απαραίτητο" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Προαιρετικό" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Σχόλιο:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Προεπισκόπηση σχολίου" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Το όνομα σας:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                          By %s:

                          \n" +"
                            \n" +msgstr "" +"

                            Από %s:

                            \n" +"
                              \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Όλα" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Όλες οι ημερομηνίες" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Σήμερα" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Τις προηγούμενες 7 ημέρες" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Αυτό το μήνα" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Αυτό το χρόνο" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ναί" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Όχι" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Άγνωστο" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "αλλάξτε το μήνυμα" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Όλες οι ημερομηνίες" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Παρακαλώ εισάγετε ένα σωστό όνομα χρήστη και κωδικό. Να σημειωθεί ότι και τα " +"δύο πεδία είναι case-sensitive." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Συνδεθείτε" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Παρακαλώ ξανασυνδεθείτε, γιατί η session σας έληξε. Μην ανησυχείτε: Η " +"submission σας έχει αποθηκευτεί." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Απ'οτι φαίνεται, ο φυλλομετρητής σας δεν έχει ρυθμιστεί να δέχεται cookies. " +"Παρακαλώ ενεργοποιείστε τα cookies, ξαναφορτώστε αυτή την σελίδα, και " +"δοκιμάστε ξανά." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Τα ονόματα των χρηστών δεν μπορόυν να περιέχουν τον χαρακτήρα '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"Η ηλεκτρονική σας διεύθυνση δεν είναι το ονόμα χρήστη σας. Δοκιμάστε '%s' " +"έναντι αυτού." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Διαχείριση του Διαδικτυακού χώρου" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "Το %(name)s \"%(obj)s\" αποθηκεύτηκε επιτυχώς." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Μπορείτε να το επεξεργαστείτε ξανα παρακάτω." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Μπορείτε να προσθέσετε ακόμα ένα %s απο κάτω." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Προσθήκη %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Προστέθηκε %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "και" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Επεξεργάσθηκε %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Διεγράφη %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Κανένα πεδίο δεν άλλαξε." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "Το %(name)s \"%(obj)s\" επεξεργάσθηκε επιτυχώς." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"Το %(name)s \"%(obj)s\" αποθηκεύθηκε επιτυχώς. Μπορείτε να το επεξεργαστείτε πάλι παρακάτω." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Αλλαγή %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Είστε σίγουρος;" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Ιστορικό Αλλαγών: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Επιλογή %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Επιλέξτε %s προς αλλαγή" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Ακέραιος" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Boolean (Είτε Αληθές ή Ψέυδές)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Ημερομηνία (χωρίς την ώρα)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Ημερομηνία (με την ώρα)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Ηλεκτρονική διεύθυνση" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Τοποθεσία Αρχείου" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Δεκαδικός αριθμός" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Σχέση με το γονεϊκό μοντέλο" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Αριθμός τηλεφώνου" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Κείμενο" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Ώρα" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Τεκμηρίωση" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Αλλαγή κωδικού" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Home" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Ιστορικό" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Ημερομηνία/Ώρα" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Χρήστης" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Δράση" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Διαχειριστής ιστοσελίδας Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Διαχείριση Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Σφάλμα Διακομιστή" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Σφάλμα Διακομιστή (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Σφάλμα Διακομιστή (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Η σελίδα δε βρέθηκε." + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Διαθέσιμα μοντέλα στην εφαρμογή %(name)s." + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Προσθήκη" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Επεξεργασία" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Δεν έχετε άδεια να επεξεργαστείτε τίποτα." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Πρόσφατες Πράξεις" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Οι πράξεις μου" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Κανένα διαθέσιμο" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Προσθήκη %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Ξεχάσατε τον κωδικό σας; " + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Καλωσήρθατε," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Διαγραφή" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Ναι, είμαι σίγουρος" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Πήγαινε" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Προβολή στην ιστοσελίδα" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Παρακαλώ διορθώστε το παρακάτω λάθος." +msgstr[1] "Παρακαλώ διορθώστε τα παρακάτω λάθη." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Σειρά" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Σειρά:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Αποθήκευση καινούριου" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Αποθήκευση και προσθήκη καινούριου." + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Αποθήκευση και συνέχεια επεξεργασίας" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Αποθήκευση" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Αλλαγή Κωδικού" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Αλλαγή κωδικού επιτυχής" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Ο κωδίκός σας άλλαξε." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Επαναφορά κωδικού" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-mail διεύθυνση:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Επαναφορά του κωδικού μου" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" +"Ευχαριστούμε που διαθέσατε χρόνο στο να βελτίωσετε την ιστοσελίδα σήμερα." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Εισαγωγή ξανά" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Παλιός κωδικός:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Νέος κωδικός:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Επιβεβαίωση κωδικού" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Αλλαγή του κωδικού μου" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                              To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                              \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Ημ/νία:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "     Ώρα:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Τρέχον:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Αλλαγή:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "" + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "Χρήστης" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "Χρήστης" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "αλλάξτε το μήνυμα" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Ο φυλλομετρητής σας δεν φαίνεται να έχει ενεργοποιημένα τα cookies. Τα " +"cookies απαιτούνται για να συνδεθείτε" + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Δευτέρα" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Τρίτη" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Τετάρτη" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Πέμπτη" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Παρασκευή" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Σάββατο" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Κυριακή" + +#: utils/dates.py:14 +msgid "January" +msgstr "Ιανουάριος" + +#: utils/dates.py:14 +msgid "February" +msgstr "Φεβρουάριος" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Μάρτιος" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Απρίλιος" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Μάιος" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Ιούνιος" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Ιούλιος" + +#: utils/dates.py:15 +msgid "August" +msgstr "Αύγουστος" + +#: utils/dates.py:15 +msgid "September" +msgstr "Σεπτέμβριος" + +#: utils/dates.py:15 +msgid "October" +msgstr "Οκτώβριος" + +#: utils/dates.py:15 +msgid "November" +msgstr "Νοέμβριος" + +#: utils/dates.py:16 +msgid "December" +msgstr "Δεκέμβριος" + +#: utils/dates.py:19 +#, fuzzy +msgid "jan" +msgstr "Ιαν" + +#: utils/dates.py:19 +msgid "feb" +msgstr "Φεβ" + +#: utils/dates.py:19 +msgid "mar" +msgstr "Μάρ" + +#: utils/dates.py:19 +msgid "apr" +msgstr "Απρ" + +#: utils/dates.py:19 +msgid "may" +msgstr "Μάι" + +#: utils/dates.py:19 +msgid "jun" +msgstr "Ιούν" + +#: utils/dates.py:20 +msgid "jul" +msgstr "Ιούλ" + +#: utils/dates.py:20 +msgid "aug" +msgstr "Αύγ" + +#: utils/dates.py:20 +msgid "sep" +msgstr "Σεπ" + +#: utils/dates.py:20 +msgid "oct" +msgstr "Οκτ" + +#: utils/dates.py:20 +msgid "nov" +msgstr "Νοέ" + +#: utils/dates.py:20 +msgid "dec" +msgstr "Δεκ" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Ιάν." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Φεβ." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Αύγ." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Σεπτ." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Οκτ." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Νοέ." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Δεκ." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "χρόνος" +msgstr[1] "χρόνια" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "μήνας" +msgstr[1] "μήνες" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "εβδομάδα" +msgstr[1] "εβδομάδες" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "ημέρα" +msgstr[1] "ημέρες" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "ώρα" +msgstr[1] "ώρες" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "λεπτό" +msgstr[1] "λεπτά" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "" + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "" + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "" + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "" + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "" + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "" + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "" + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "" + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "" + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "" + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "" + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "" + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "" + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "" + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Εισάγετε ένα σωστό e-mail." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "" + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "" + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "" + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Η διεύθυνση (URL) %s είναι χαλασμένη σύνδεση." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "" + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "" + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "" + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Παρακαλώ συμπληρώστε και τα δύο πεδία ή αφήστε τα και τα δύο άδεια." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "" + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "" + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Παρακαλώ είσάγετε έναν έγκυρο δεκαδίκο αριθμό." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Σιγουρευτείτε ότι το αρχείο που ανεβάζετε είναι %s bytes τουλάχιστον." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Σιγουρευτείτε ότι το αρχείο που ανεβάζετε έχει μέγεθος μέχρι %s bytes." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Η διάταξη αυτού του πεδίου έιναι λάθος" + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Αυτό το πεδίο είναι άκυρο" + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "" + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Αυτό το πεδίο είναι απαραίτητο" + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "" + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "Boolean (Είτε Αληθές ή Ψευδές)" + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "Αυτό το πεδίο δεν μπορεί να είναι κενό (null)" + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Εισάγετε ένα έγκυρο όνομα αρχείου" + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Παρακαλώ εισάγετε ένα/μία έγκυρο/η %s" + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr "Ξεχωρίστε πολλαπλές ΙDs με κόμματα" + +#: db/models/fields/related.py:581 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "" + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "" + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "" + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "" + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "" + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "ναί,όχι,ίσως" + +#~ msgid "Comment" +#~ msgstr "Σχόλιο" + +#~ msgid "Comments" +#~ msgstr "Σχόλια" diff --git a/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/el/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..7d43b315fe3e5985cdbcc02df9692d09ca67bce3 GIT binary patch literal 1810 zcwSwS%WoS+9LI+|Tw>ngrAky92~|*)HFl&zFaco_r%kEjC^oH%#9_0Z*h|*C(d=#@ z5{J+xjX+7IO07^UDgvp>fh5H#RqD-BBJeiJFEYeHB3; z2j{@2!Da9%@Mcd~-!}LxxEJvk@G$sCPuTyV-q7wS_$!zM`@wiDjC&*&=1D~ItFbWt zDexumeQ+B5EEdk|UM!6FPvqBkh!8nq*}JT-{m6UhB?q6!NSH&;NA@b~kh76-5QbnR zDu{3$;emcd??yKAz!ahJyA`NOsPaNbGm8iQx>B~T{5g&%r5C18{1$O8Ll{v z%cxF0v&!yEmO5~mtz0ao^H66w#tb>F+rG}{=_s4xYLnBsQK6YxZd%yS)6>4q@UrG< z#xMJ>M+|Kj zp3ku%MJIe$9nO*cJaav!4kozs0di_%CG9NZuIbomI*}QECv!4O&t@kJWBKv;B%5`( zr{&zTS<>G2%dS>*(lkDiFKWY_=^mW45#3{HI*?4B&{6|h@->=DrB9@eCsWBJ47Eu% zXSyA`^V!U#mdk4+8G0(8&W#;^FF&4*pV3`UD{|d-E!}f?n&vs`G+lWIGmcM(oKSu% zVxr97gp*zN^)l0nOs}TtNzT|zJU5ohcKS&rlJQ~3_Lzl;&bfk(p0p96>%3Q`$Sw4&0t9?+g>fPDSiwVMV$thp|25fEm*)w z3x0RS78NUS|5WUC^z~p_<`R4Gs;NvjRQ4L&8>nD6SP3rB;J)8iQ3VYmuETH-wJ(Vc zX^hq=>{T!2-_iAb;n)y}b^sBJcsR)$v?KCx{+gL^!2dM-vMW zHZh{@PRcJaoW^7wx5x3eCz7kx* zAnrg=78oF+QgKWD*VKQ#Gw=5OAZblBBsSFSl}2{i#5Zw6-KxCMro1%N2*skxTtiqn zI*2>i;$$%zEyOk1h?<2G*fQ&alOK, 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Orestis Markou \n" +"Language-Team: Greek\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Διαθέσιμο %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Επιλογή Όλων" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Προσθήκη" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Αφαίρεση" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Επιλεχθέντα %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Επιλέξτε και κάντε κλικ." + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Καθαρισμός όλων" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "Ιανουάριος Φεβρουάριος Μάρτιος Απρίλιος Μάιος Ιούνιος Ιούλιος Αύγουστος Σεπτέμβριος Οκτώβριος Νοέμβριος " +"Δεκέμβριος" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Κυριακή Δευτέρα Τρίτη Τετάρτη Πέμπτη Παρασκευή Σάββατο" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "Κ Δ Τ Τ Π Π Σ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Τώρα" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Ρολόι" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Διαλέξτε ώρα" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Μεσάνυχτα" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 π.μ." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Μεσημέρι" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Άκυρο" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Σήμερα" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Ημερολόγιο" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Χθες" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Αύριο" diff --git a/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..6c4dbe4b4ef18e42500d5eb273abe9a38f71ee2b GIT binary patch literal 627 zcwSYH&raJw5XP7PsyUR>OAkHl9hMqss?-I<5-(ukwIgStO)t@sEXGmrM(YiNci_sq zz$5eldgab5aAHi10xSJI^L_e8o_ScFy+!aAfUkf67J(Zyes{n};J)(z0iS{Yz!LCS z`A@(nV15dr8t?`906Llm59*)kvF<>ffto>u2)d}WuJjxLEt*A>SI*B={lcs1Q^7>Q zS!X{^I2AAYUH>}}hn;caI1CYwBhWr=zlPopw;4@3<3!~&jqaIyi+QUvW7NlX6v@k?`X_bdJT=xfYf0B(FUaxuI zH&, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-09-25 15:43+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                              By %s:

                              \n" +"
                                \n" +msgstr "" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "" + +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:59 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "" + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:17 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "" + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:22 +msgid "You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "" + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "" + +#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 +msgid "and" +msgstr "" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "" + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "" + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "" + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "" + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "" + +#: contrib/admin/views/auth.py:28 +msgid "Add user" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "" + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "" + +#: contrib/auth/models.py:256 +msgid "message" +msgstr "" + +#: contrib/auth/forms.py:52 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "" + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "" + +#: utils/dates.py:14 +msgid "January" +msgstr "" + +#: utils/dates.py:14 +msgid "February" +msgstr "" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "" + +#: utils/dates.py:15 +msgid "August" +msgstr "" + +#: utils/dates.py:15 +msgid "September" +msgstr "" + +#: utils/dates.py:15 +msgid "October" +msgstr "" + +#: utils/dates.py:15 +msgid "November" +msgstr "" + +#: utils/dates.py:16 +msgid "December" +msgstr "" + +#: utils/dates.py:19 +msgid "jan" +msgstr "" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +msgid "may" +msgstr "" + +#: utils/dates.py:19 +msgid "jun" +msgstr "" + +#: utils/dates.py:20 +msgid "jul" +msgstr "" + +#: utils/dates.py:20 +msgid "aug" +msgstr "" + +#: utils/dates.py:20 +msgid "sep" +msgstr "" + +#: utils/dates.py:20 +msgid "oct" +msgstr "" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "" +msgstr[1] "" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "F j" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Brazilian" +msgstr "" + +#: conf/global_settings.py:60 +msgid "Romanian" +msgstr "" + +#: conf/global_settings.py:61 +msgid "Russian" +msgstr "" + +#: conf/global_settings.py:62 +msgid "Slovak" +msgstr "" + +#: conf/global_settings.py:63 +msgid "Slovenian" +msgstr "" + +#: conf/global_settings.py:64 +msgid "Serbian" +msgstr "" + +#: conf/global_settings.py:65 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:66 +msgid "Tamil" +msgstr "" + +#: conf/global_settings.py:67 +msgid "Turkish" +msgstr "" + +#: conf/global_settings.py:68 +msgid "Ukrainian" +msgstr "" + +#: conf/global_settings.py:69 +msgid "Simplified Chinese" +msgstr "" + +#: conf/global_settings.py:70 +msgid "Traditional Chinese" +msgstr "" + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "" + +#: core/validators.py:67 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" + +#: core/validators.py:71 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "" + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "" + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "" + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "" + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "" + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "" + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "" + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "" + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "" + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "" + +#: core/validators.py:138 +msgid "Year must be 1900 or later." +msgstr "" + +#: core/validators.py:142 +#, python-format +msgid "Invalid date: %s." +msgstr "" + +#: core/validators.py:146 db/models/fields/__init__.py:415 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "" + +#: core/validators.py:151 +msgid "Enter a valid time in HH:MM format." +msgstr "" + +#: core/validators.py:155 db/models/fields/__init__.py:477 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "" + +#: core/validators.py:160 +msgid "Enter a valid e-mail address." +msgstr "" + +#: core/validators.py:172 core/validators.py:401 forms/__init__.py:661 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: core/validators.py:176 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: core/validators.py:183 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "" + +#: core/validators.py:187 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" + +#: core/validators.py:195 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "" + +#: core/validators.py:199 +msgid "A valid URL is required." +msgstr "" + +#: core/validators.py:213 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" + +#: core/validators.py:220 +#, python-format +msgid "Badly formed XML: %s" +msgstr "" + +#: core/validators.py:230 +#, python-format +msgid "Invalid URL: %s" +msgstr "" + +#: core/validators.py:234 core/validators.py:236 +#, python-format +msgid "The URL %s is a broken link." +msgstr "" + +#: core/validators.py:242 +msgid "Enter a valid U.S. state abbreviation." +msgstr "" + +#: core/validators.py:256 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:263 +#, python-format +msgid "This field must match the '%s' field." +msgstr "" + +#: core/validators.py:282 +msgid "Please enter something for at least one field." +msgstr "" + +#: core/validators.py:291 core/validators.py:302 +msgid "Please enter both fields or leave them both empty." +msgstr "" + +#: core/validators.py:309 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "" + +#: core/validators.py:321 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "" + +#: core/validators.py:340 +msgid "Duplicate values are not allowed." +msgstr "" + +#: core/validators.py:363 +#, python-format +msgid "This value must be a power of %s." +msgstr "" + +#: core/validators.py:374 +msgid "Please enter a valid decimal number." +msgstr "" + +#: core/validators.py:378 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:381 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:384 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: core/validators.py:394 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" + +#: core/validators.py:395 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" + +#: core/validators.py:412 +msgid "The format for this field is wrong." +msgstr "" + +#: core/validators.py:427 +msgid "This field is invalid." +msgstr "" + +#: core/validators.py:463 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "" + +#: core/validators.py:466 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" + +#: core/validators.py:499 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" + +#: core/validators.py:503 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:508 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:513 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:517 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:522 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "" + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "" + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "" + +#: db/models/fields/__init__.py:340 +msgid "This value must be an integer." +msgstr "" + +#: db/models/fields/__init__.py:372 +msgid "This value must be either True or False." +msgstr "" + +#: db/models/fields/__init__.py:388 +msgid "This field cannot be null." +msgstr "" + +#: db/models/fields/__init__.py:571 +msgid "Enter a valid filename." +msgstr "" + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "" + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "" + +#: db/models/fields/related.py:620 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "" + +#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" + +#: forms/__init__.py:663 +msgid "The submitted file is empty." +msgstr "" + +#: forms/__init__.py:719 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "" + +#: forms/__init__.py:729 +msgid "Enter a positive number." +msgstr "" + +#: forms/__init__.py:739 +msgid "Enter a whole number between 0 and 32,767." +msgstr "" + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "" diff --git a/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/en/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..b51ec1c7026963f4ab75e0f0e0b42a1a55077fe7 GIT binary patch literal 367 zcwR-1!A=4(5QZ^&+M{O=J$S>>E(r-#BviKI#&(x13yIz;l(hz1(iWo+;_LY=mT2Tp zzSDe@`RDue5$_-8k(3IschD`>K`!@)azU^V%AMrgZ#3Ck!oE zHOtE)snpiG0KzwV1-HA#Lv0C4rezIdr(}prkjbD3>R{Z3v;(-0(v)=5Hcl~)B2?;K z`-At$Xa%B#jH3IJh`jw&7A)b?2dJGiej|f*1l`R!M+FrO4Va)gAJHtU*lae8s%dtz zQBW)SMi8gPWWKLF;C!^vm)14IMC5`Uo=PuqGPWit6K{NOV~L, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "" diff --git a/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..e9105aa64c1b2a4e0173dba97d493feff276f35d GIT binary patch literal 40248 zcwV)=37i~9bwAz<35QH#!ks{%EfCr@yDQ0;6YkwAdpfC-0xNtBR)LiA6J0tEkw^ZUN{ecjWu zJ1fZvzxlZLbyZirs(SV6T~#Ij?X!OEZU?{bb~(FW}vl{hX9`p_)frQ0KUiaKlTc?_bI@q1AgAhea+Gjy@LJvzLonq z;4@`F=)|uB{8lgVJRk72GA_X106Yx%Y`_onvj2Yq_-lYavGP9$e2$C{@Tq{$?qk1R z2>4>aZoroTZs=n_!akOp>|_1cS^gUVp9OfE!FK|l4){PH+yA1~`!3*VfJXoufXDhs zpHe@^^%}t60=y0oQ|H{%&-UL9xB&1g{T%PZ{lx$Me&YLaKim0vKl^ps64rm#64L36 zCH#KY683lf65<(ILV8@fgzfLNdX*)_lP)3t8Nd$%UJp0|cSy^QUDVj0_ea2dz*Rr~%3ps?)YfC~X%bQbBi1`s53 zHk?KNYXYK(^G-kv-T4;aWq?n{WIrG93cw2i8)uWw51dVY{yN~x0iSaY=j#H%Zon<) zkWN$QkY8>(hxI;t4*T<+bBOn6fG-97&2x>e=aP@s1A-LJPCzUH=jL-smrtBad|x}4 z?RK5VboY7e{}q6*1iTfn2Kc@6I4>KQb6$o4R{&nRobBAUob&ju<(#JvFDHE-0Ne)n z;BxZIvsbYHF9w_hT)x7}8+>2|@qfnBUj&4>aK5^N^Yd4h|FFSFR|$@;|zoc>ZoR`}reF|J!P|`=6G6>KfAHnQJ&tXRP6RUbcqvaq}9M-wXKLfHP~@ z-@DdOZoJLl2iCBEA78_H_yQoBa2^8OA^0!kI^Bam+X4UJLelZcm|Pd|MS%AJt^&Ls z@LPb_0Pehq^6_s0&yw;N+qeO*1@r;m1b7s%47hKd@$)+NkV&Ud)qfquI}Ez@w{dO`RaxZ?8l)E9LKu=w*Y?3 zzCURr+kNgv(xYc1@h;s+It>7Vg`KwnegyFQ8`<7XL)7_L+s~Y z4srYs4RKuG9wOg7cN1itv;(*s@Rm)SuVb4i|6cS;%FhXd2VY6P{mLs@?#L^-uAjM? z>9aPop0}Cn^Fy1te*SSY`+52>#sb(oO#7xfOu7AuVSfMqFw6fZ;3B}6Z6SYc+QRfh zTR3k&-$J?iTR^fz@Bl&uI0pd#5b&#)aD1;HVSn!%p`N&Zgnaqs5z3KYju7wJucBPo z@ha-83Lr@2JoqZ||5LZJo!whG-rKfvz1v8jTu7@kPv!65DDR*ACo$cMcoqGI_w{!l#4G7WT90%M5xcoB8 z@1rQO`Vi2j_Wk2kHLm9pw8P4c@(j>*5^--?xMP_{a|S?_-w!qQNik zFgd@2<9K8T>G@BVK4$5k?I3*~-@);mb~)So?aMh|i!NvR^Digfi!Num^_R2Wn3eY~ zXM45FDetbb{F|-by_d7S!%H9IvYo`Y($ed9vOlld$$oeSeS^VH_OrH=?M+*~>kZEx_Wh8h-)r^mH~7F# zj_Z>*|%zw@m#D9jtS6sn%&$V)Eui$tG zuVDYS*!LY*kUstulnbfVzsca;27k}s`wf1~;DZML+~7mj&i4)P53HS^*!Rb;pxybj zU0h!;-o<&@vWxv1+r|Fw-bK2UcX51scM;G2UBrLgF4Ex^tN#`&ckeFR1@E@+AK%4( ze0mq<_Lr^vWA@#-lKuMiE7|_(SF--^T*-D`aV5*2V{pLAU1;!?SF+tp0k4JsS_b?G z;173G-X4Hbel_5SJ^G)gmna9467kfD zCv06#uz%-H zaQ@d#uwTP~9|hb22vzGW2rzemR{+L1qR=-mv+K;4L&2JAGjtYz7K`e<6jSH z2mEf0ahsJj+KG46$e&-SvHm~R*v}VWa^M;`=SJ*r8qrR=J0f5IWkf&vClTl8B{BPX zK}`E%N6htdOH6#9j=7G$6SLm4>a-Kr)Y;Beb<*`yfO}*d`#3LC`#7&x?PFZ%x_z{Z zKfI58^7(zF%QyFN-oFP36wcr8^3VHH`n&h1)E7Skd=22!8?5)n2G_^U4bH>c8eET` zY_R^<8l=Ow8yw&F8*Jx@rH?k)kADM%De7F*Wd7|<^3^>}^PifOPhV(S{HMwF@Z%=^ zpuCf*st@aXh*M}qFw#wDbo9sQ!MxGDf5>M z9-X2e`!g%|iz)Ks^Y(N6d-s#SuHR33eP}=X^QZgS-e2rzzrJSazp?L+?B}}u!G8L6 z$M=)ZUVur5tLO|)ldf-^rk#0kn)LnLH0R+f(`L6#lfNFZ^rO?%hrgUA-JUf=Ik#|z z@_1l|`LCWK|JP@T@75Xi=k6J{^R5}<{m=}@^=C7*b040eKhysj^8c-XuLJ!1*H8}Z zzKZMUTEMph-fwX0YYC6PmU{ZG*U_)|!t1CRK&Qo-?*&SC?Pu^|c|MP0%JM9|I$Fr_s`uPT5d=1CF z=o-q2fosU`mtI4B6W6fac>x>GKS(yqt4fHSn7lk>(~=}-Lx5GJ&9-fb-RmfP6h&)&xV z{_r-gi>KU9eRbyTq{pt?x$dvKo$~7aw{zS-x}D=&a)-_H9pszq?jXP3bce~yJ80M5 za|ikJJ}dV@`~I;zI8UFnazD6({QILj%znOu>+A75NQa5Ha9lUO#r&|h5bxt}VLzUE zC*upheJA;2>`uy&oA0Fkc-ZoP^DfeT{axmt+{O0pzl;5P(DFZjm+gDp#d&+^F3Q>C zcTvv&>@M09PraLbapv8m=Z3qv?#Az?9rMAv8Si+}JzQtuJ*4;4J(Saj?%}-t`#q%p zX$RQv)d#rVwj3bcb{*ijD+jnP!UL2y(E;}JI)gVHF#UIc{e0U2w)>F-9OtJFaC~2~ za^E<>_4^N&{^m-ue7dT+az{eI`Y z9LEQ&-u?HQy5!D)ln8@@ZO+&k~%_}^prKX@PK<&W-T{~o-L`tz@? z-oF_9g~6vCB7DBVmm56S;5vh&he(goA<`kT^y?1M-g)yO^4qCd9?xLQQ-@6_9rt-VZ;D0#O zlbcIh4C%xqJEM!3l-`-z3@$bn~{d8pt@Clk2|yrq2?X9RL%nq z?WS9#9G*M6s6St0+u3SPeS^i9NJkoTlw3%Xpdcr=lAh`O`$)3 z+In^Vq>J|K4!}QixaaI!*#_XQF2>2<5BMp-pF6Zmp5I0PP=395f1cHOCmxu7&R^oW zpo_Ml{N9cSDq6x*&WDhHu9bfl;9udX;`u{7mv?c$UVi^-@J+~jDxRHqzSKp1E4I%o zyJ$oFIpF8JxbCyxjRqg=q8<3xc)ry|S+))E-wI_v>ClFIho%41;GbF_R{^fT^Y705 zzjN^Y+xGrNykCLmeGYZSCOnf}w6n4w%pPYW@_uUR!v^;ld?CJl8Sp1~UXJ$^&(|IL zIzxDm@H}W`#c%ln(!X$M(`LW_#`Bskr|q}2i+25)U9`KNYWWb8&fQk_e#;a4n2rN3+NG=uNK^G!UL%DmzEGM*0` z&PT1x2a%q^v$l&q+Y12utW5U%9MYTc{0Q$~vpl#I&T8cSgF~Brd{!ONt5@Us2A(VN z4X&*2(S8fhSxEm@q5OXu)27`K2M?W{xIJMnzl>TCQG z&-X38ztGM;y!Yd|4$oKdJh_W;x3}5%`>gCY0AFSA=K+2U&&OM6oQsiu2Ob~qD+~BP zjOWLAK3>R^IL6^F?n{eJehJ=Bx9_)R;{n`l?_&Ras*7>&MFl+X?&3MGEnSQm9m6w> z_ZN3DrXl{+7ai{3%I_hEKCArxe>`^(DoN7|&lK{SrK9Sea#b z{s2!`7vrb$`wpJJ!}Ino?sX=B-?Z}ojOP`2zaGyQto-lc{V(vucy2EAr5o>4_WlQ! z?*cxA=Wi^1BjD!&|H#s@m6zCw*uO`d)=&IU@e#$3dIO#bJY%T$+g*&^tg~`|RH!39 z{q^=PahkC(p#cmL#4KEDc zQXKECdCA_;Z=}6$CGo0mW5RdCAlmCTVs|=jCV823K_iaCl9%+lapERHb)wN%4ukUE zRuwO*xL&2=293G3``s<^l;pW(FItfLp8dHG{<0cF={A?@k8(R1j5jOxA{5j%)R1!%y6bp9a;) zYxtD~Ug|CkB4kEhxX|qeiCSbxr_+Xyij`Ur1!*Jk8lXZjt}S_qA1y||nrJx~U!di4 z{6P_EPyfm#_0N*P-b$g9Q(vvQ9<48#AqmTf61R5^_ zNhZB80F_ZJC^dyJ-J7Kx<}<qZ z(^Cy5{Yc5;hn1eRU$E30#Mp*L3s`xe%nfi%zD|IWepSn@p zaJ_on_Y&h0&uu1Q9aP0Mx$L^L%;gw?Qb#EmEGusldU zM=IBXG?m`O$j5MN=wimKh1pC`PX4v7j3pxnR)Zvk7IP@E*5#~j+qzkLkhO*nv9ygyfO%MoDdGRF&HNsFHw}NGY0#O`I*WzZX#Zp<&LeNPpP<)%#QqJ0X-CdL@ zrN==%G*wRmU`f1EPzK-{)g>b@Q(mM(qXNO8>s-jkiP&v34LbN~*{l1N-5GN&T;oc& zC=1X|7{63K%{0>|1{HvbCFjGr-iX==*4Bjn+Jrj7x_^YGvZuS zkXVukrRDBH%qS*yENLo-toOpy@9Frtmmj6n7Vr}aCEiRB243V`>|q%SobaNmZ$R3% zH>@AXt?7Y6R!yXBnoh+@CCi#GU8pu-kojiR#Gm-=-t z;bdcpR>6<@0a($+I2sR-=)R{PAlgEKs}zH7|6drX_{H4RB)gJyU`Dr6i%46CST*7_v3RqA6E~syeT%@=-;r=)nmObgcV{$~*|e_GR5#rJ@Z|S-gf) zhG)7srEF7Un^{$TcikQ=hL{NY-EC2B-ze`Z!^%K&Vj9V0h6*)lSBPx3Ts2tq zld~=^H-)S1dok5!#?AUf-HKj_%yQN>g;K(2Pz_BC4VDX)q0EWshIP#bIDD|L<^`eH za$tmXR&u}@tOPbT&Yas_<{B|s*wdjzOH$%%t=^cPrwcA94e}30)le4AV5BNSQE3Ho zLc@hrOdIZ^id&w*`Y%H}r~UJnuEnQP7%x@m(1y-6rWmw?(!>J8EH9d`MhKFeu(+;~ zhQGhjUK+MY-kT>5BGntoLSIW36!C`S!T}_LKa$w~W%WNU2 z$KX6zVfRqHi673`F!B6SvqjT;Ashfjb<>&*V(x2=rX zUt!<-LgU%_S?ecivQuk1&#la<3D^cUMLF&J&OWPm`FShU7t`0}C#kUXq(xh~GXZ=_ znNV}h*md_Z`3d{$OP~D7>ckPGawVt+ux4AT8aBD8elMq(dS_z9=4|Wb7ML*bq@M)i zBDo3-(rE_#`C8fU)#{-?;4WSQtj)%fG7JGMrzMN~-C=L9PeT;zR&^I-OGTwL2m^@> z(+Y_cKJlmDs|Vf{=7u%~-DR(g|a-C!jaQC0kL z54zW|IP3kA{z;l%GL08Mgk|DuU(2-4OuQT2Fz9Nm3NUU7{sCvbxJ;?ICW|teoFX$6 ziZ>?Y4I~AK)Kpm?Bx$49^=;Z)E(Qpawbbv9P54o-h^)o5(Y#Wq{qjZltB0d92R@vr zYm6fF=#HVGIH|_)wjxTr4Ck78Ke(K8&1|1?v<$`3t9_gDr7Otw8}0S=tJM?wi-Z(c zjNU!E2JHpCw>}9f-n6s96MF(?^afZP0%B(aXucPJ<7Pc|Hex#bDQ6>%X=h^$iwTM? zauWv7CdSeVDZ(|c4LLw~>jpW*Qs0_<1 zs=#Y%aTMkr7PoMdE-=_4Fva_@D(R7BR&zEZ5(Lq4D}OpewR)U1#N(nvHxx-@LBotS zEm*L#Fa~I~+`1wUXQq%g1$QKNtA1mSiB?U}3YMa9g;$8uDCm)j>I~lYYY>9%Fle%5YXXSX_Qab6|#h=_f6tmUFPF8TpR-`rsvR z?|gR9X01Z0>%alrT*14S6@H4~zg#8Wamls#qfgR0Gh69L=PuS;gUH;{KZezs%aC3ZhtuqZbZyE{%2E|}rx+&l3}N_^EpUg{t=*CNNLUDd zC5~j;TC!KW4f7Oj`qZ2noXQP7tn+ChVJU!@eG1iR@y)80V2uxBfe%B6h46=J0_hGF zLKRAB314*M@9xlpfaytC2dF0@LzGgc1GDgnR0(qxC8Zf7q|Gl@x=Oy`I1&*vJnb?J zlR8@p*4luxCB}d5IK?$3Jj`;%46TtPnN0arnZGUZqm4Yd3G8-#yU;Y@n$&t zQ>E}NobGRZR>e0f*$K~pyfxhAkfYP7Gt!dR@}A1DK<@F_8JUoIx9CDK0iDnUf|4!* zGx^l)%Zyof;i7aQ1-wdZbdpl09L6c*0L@6>CH|=4X$_sKd~FM!o;)I&VUFQ~+Ma?& zSZEmZ&-*={RUor=vV!wiJ*|C+QjESBn+b=AEbM~@q3<*YFIAT)t&SgB8h@Swm9}UP zDUliK;$X=NCN=?Li!ULOSyg18%VLqJk*h*khKgubapSLUk?vSdL+esC(JfN3s9RxA(rHzm0)n)F zL72TW3RTB(LGsgmOZ{>)2^!Ojp}nBG&6I%qP^{J#wP;KQWi0v_CcQ@6)B&94z8*B3 z?lD64*jf!+qzem{QZH6`*%SYYTX;U>krET~_t$kZ#9bGQdO({J72l!7L{+p(YFs+S zY{^(-4N8p&Pk3&9B5uT*ptG56Gs#pf>J}TdCta{Wc8}D6mBd7&Q6E^c1YhHtXn_=U zk`S6~MB*PHfR%N&>gHAIZ1u}xl4iETRtdtInO^raON%NDvoP+% zVC#3+ijwP~`PGx?He0SYbJ}I5pvaIV+iGb~KUxsAQrtG!iovKkx3vkn0gif&W+G;wdp__+kNC zcvq6-HdmS3Bze}NJf7VOb&rPeq_=RVB||)44mUMMpzTaj(IC?{A#kW{@^Yab+4tM-BceP}qiQYu8FF8Mix=TyW=N zEJ#Sj7fN?wHMZ^&=96d8GurmQ`S&)g~GDdhM?7o zL|?s%@*^bBY~&)3FcG$?Ioc2l)$N7{E)nYPnzujnqiSQKC&i$e>X(dyoKm*9zcuF7 zg3uWQVI8cEXfJo{)4Ct2$DzkDBoEeE66Nm z8;1?wt64T=O9@td_C=c|-*&{(p|~}Af)dSoWgdibw81o}?BF(&$XB}}pFi90?Hdz8 zHi4A_u30P^S+jW@WpM#l;e1Fv4tOLXlk2Z)2IakD!rv0ejZd)_EJ6wQ6v|3`LIN~< zePzDA{MaUTDnX?XGj@IM{V%T5NLVb&e6ehNggPwE&8WvDMe7 zB+BK!(%8x?K8xz-TZd|FO1D+mvAelHoyT0vf|ZZ5_p`WY&MnR?UmFEs>=G^p+=Sm9TX=PWt5Snv$Tx3dXs^dOBaaKbV9eeOJYgy_cfh}73mfv zlm(C*fghE11~EvPLLMAS^OHlfrc$QYHpJD^hIOUhAP+ovvT(Br6_Hj-<4`1#gyG%p zY}cblrr2|Z)wRuB6RPl8Y-Z%*468AKOyqdzX;iRaj}C|tDtZxi=A82N+$FrM zKMQ*Bpjt5Q=4aKUO6DJ>r(Q++HGehbf92@Ki_*oJ&!?qo5MUF0wRPOCE3+Wd1h*(; zwTLt+!BCjaLSj@kvM_X&7Y{!uVmm(93+yKAcEy;U|$t<^T0<9p$V zmBVHwJEB6j$kk0#x3FpFPs#)@Nqq_7=6p}j7W2DUX)H%BlR!dBXu-!o<-~fMb_0XBZs)PVZdOFwfJDdXRSTKqUJ4l^6 zpj(nbWE(`8SC_@_X1R4wuzGK?uJm(ax(;vte}x7fr*5zcx_qxy(M@C4@zzXIu~0<= z>z@HQ*{<L}3OYtfUN7aD;Rw=vtn%=B98xVDbL zwkd7bQntb@!%K>LKth$cT2+!q{lq?N#*ELnk!Y-s6`o8}>*mKzwJul4jyd7VKsTk3 zwL8m#KHo7r7)l9YN;8OB!ArL-h?>#W+H9+x?+ETF_P1AKp*99{+uiX-IuEPWhdV})idRG2v}#qDVPdQ`;8i|?ruig|(+ z5JY^{u}X*>eM$l;VDHmT4N4YE+sP&w)*!4j1F557<^cum``R6{q_o(l`Zsm7(I}pD zY->dgE~gcDv^RL4_9j6^CoVUfJi^=(S%ueB-c!i6l;f&HhJjVIr@ZpSd^2QUx-5S? zD}OsXe>*3CJ2!tjFMnH}zpZevdIMRw`HOB_wo;8ae@`8Tp&0AT1(TBxA~J^OIVfTK!8dP5ruK`xzP3s9>Jh>rZFzs4nVO{@_&>wwgjsB$}Y0vb1G1yy}3{ zfGaQEBlV~(=jpCAxtn2>th0_@XL1Cq{-NZ_-%`F7XEBpv`jR>l;uvtIG+46yQ4q~=W;6s@Urwgl-rC& z)dvGKg2jpzFyIb*Nf5gm;%LST{TX*l^dE+JJj8 zQ2Aj9o$IdT@$bH&Wh=H^SdHUq=u>d8;_%S$U~9z7`j;*!hPejRW>|t545ACvR)bz$ zH8grj--;FIp5M2uRam+`?k9bNc6MaIT~P`e3r50b;)Q+dVJJeYxlvuy>8i6XaQV8b zJ95uhFV8Ptmg(I*HE%@yvZ70)vp_>y75-g1M=|{4gD?x{*Taph8Do|VAWSj(z z7-eIKz)-jCBPm{r8^`4`0v6RoT&NF2<FZ8y9-S;y%6hCxbg@jrRVzEq-^8I{R=KBrQAZ14G@^&%_x?jB~YT3wgJM!hSi(G z?w6wYepC*DMU{9#hRqsmMlP=hdKKLqeH65+#F7_hcLn2xZuf*}u?0c^(5My9s;Hy| zLODVz=Yaxo2GQIRpsF^Lm|t}W&FL88-<5qkyLdQ)+H-Fn4u-|}IO>O<8y<^%uN4{B zHl*twgXgh3c?9Yxmgsm^7=k14XT-5iW<|(Rq<9hWge?G1L$;e59-Ht^t%)akF_Cj9 zF=xA$IPLN7oNh~J-}1|dJF>5C@miv?XQ3ps`<*&kZ_1>$?jzQf@(js2i*&fMg)7_K z;I1a`9fGW_n~3=pL?CkJ-a92#7JXza&9tc^FQ-ow*Gl4yjr!R#(H=^0(|p!)6A7S*!ZPht@QjTgxTRb%}F$*0b#9Cp#of=XPt;CQU#1;^O!hRUkQ4vg@c1h(Ry zP9J%@QOay-hOu65OyPe&8uN1Fkx0%~>SchW^(L*i5)$=1o&;s~>SC`}3dCIwL{q`# z$uc^m2HFq}_H3CUHIRL^ckp@D7{8x6o#Cnn)(r zT{tLV69DN&ua|s0r{VpJtBcSzW zMfQ9eauykeT}5YI@Aj4wvQ^ko$~5Iq*d~I9nd8`MQ4eAQiNd(jozy zBuO?0S*EKecu(O*qd2uOX7iXQeKz_kmsk9b3(6J==B?Zm8q2bmU7##-qH^0SuIs`I zZ3K1N2ZnjT8IrU6US<>cxo9NHMlACxNDR4PldbNc8Xb|7y0IQs3Xhe8hNrXCpJ62P zW$4V%5!saF`YW?KX07jE1qyeapVspA5GzY4n^#&Q&oc^w%i85jCA%^yM?yNGFOKQb;KDGp*ijQq=Wyz4de~(yGX&3W zT*9F<11wuE6nQ=XX&Yxp8e5yQ|^V*Y0ideS3=#wh0sFH^f+Wo8WTJ3d8N4-j{qE+b zUq!`wT)|L$Q-gYKv!S9VuICe$OH|(8qS2Z-pWO&@$KfO<5o2jI3+UI#$JRG7;>0Lk z;}+Skd9tP7i*Cy~HVM9`1(7dP9*T(s(aCT1Y;neNi`0wCM|GDoLI33*X^5d_X2nI# zRGgL#Ng$aUk|s#byQHz+B~_ON;}omC>WQ12k^|cAl@_j*s+Y!lrMd|8W~p*bqMBP~ zA<{Jq3ma`bCbvtA=F8BCGnC5ZQgj;3r!l0O&{?=n7im~@o3mIp*J?%lQwfaaj$}X0 zdbmK@1yf)CIU(Qr(q`nUfvcE!y;M!x-dPs1aPH9vDm)Ta>0l}w)~eD{O;CU=`j$CR zvb8S@i>sb@OV@GUbN4IC#W=l|S;cwFGzLPwe=kTkd06|P+oom++HOWiLg5hagnl)0 z_>H~HPfu$M#(hk+4wqkl3*|gfN^Pk!+u%jsq?#|vs-9gw)%&JFY!;=Guc6l(!iG^% zqC4P}ii8Z*`Az-yl$4T15IbX1JyM4@6>&SN0JEgoP?#eOYncHSVfj@xYMF&8v-_yR z+RC_|+(;E4JiC($r}WsQADUp+psw6VMMtX5HlrRKBNFH+COVAsF80qdo#8BqfJXNo zOUunrr2HQD3Y1=-Uq+2PSgB#I;x!pf!iAh|RND>HPDbwh)iu&vJF-8h#RUwIa5J+0 zr(=o7>Nb`h({5c-)qqth|EftV4i%d-?iC_cb1$ySEC(q#HqcY%9jauJ%fU7Wnf+kU z8G?6nQ{>ukpl2LaNIMscc%^vEZm&*wrgYFb`Nw!3lg(*}glt;0`;*1bMkv|Fuho;L zUsCdkFU@sY$(4J-&cH;fX4qn$Ysw&=95;VVO@??D8?;P*p<~pn9Tka-F>gUHBnH|d zk=$_=HI?6RwFpIa#kDC34jp1nJeL7yTOLJizq;xdZ@8XHAve3_~ zZY&Kw+d^?hT2ihI5zpvs9=*?Q$W}T;@lP!{U&Ie%CD`N9s5IWOGu54n&EhdFsFyHX zMcXaF%v0%T3Kq>ky-2Hue61Ac+?&dFm)xOMIps+_!DRN5qWGS-`9&Rw+FqpS_YIBY zUe~-Bqqd-=Vb_g~R){1uZ$_4}?l}F5@TI?MgjuOy5xV?x?g?4qiD3Ly5BPsh!M5^= z4_hpn#j8?dPnMU3{SlBXEwLfNt(paTNsWi*FJCo^GKNjUNU>b*VoX(?I zZA#P7E8AUPi!Q~DxSDw5$2|B0v}NjXYlD~i{C{ugc*-@uafwBC0GrD)*EjJw={oDuIzW!I+$e=E(RCZELe8zSGtwl>6wB5=aH-*DI+jEIcULfnjw zO~&OoBljsg#B#V_{!L6?E0z^(yGO+v#mW+LrMPS_;A?S68~X46+HQa}1-fDJ%y{Mt zbvlcJX`rhd(97~u8w+rpn%%WFRLhCD_B5-)#Mv5cxiD^9ECS0+FB@l;Y%Wd}T?}EZ z8}EW8%#T_X-2b*+*(_w~nXM%Rmz&03%3TEc*T&@5YsB5>vfd_6MR1$+ZNp|egmPXb z^d+z@BQ9)vv^Ku8QwWe<;x@S5nYY`1>v$a|L3aV0vhO@nxN59xy&PBsK$!_-=G0Bd zQmt@X-ph_wP5C8vBA)Jd-chV5@luf#vh~7(XP|41lgIv}Y|luem)r*~n6jSUM3&Xx z>Es9XNGn{nMjZP5+|&`q5>A5dvit3ou*e_`FD~9go^t`YclI26Evzbv zJULD#5kFyyV->J!;WDzBee+fDc;P7OfLYa^YbPm_SY+(r{BlVTOBF&9ypEjNE6`kl zyO@FT#V0~j;BRQOBQ;-EW`0dM%HptYH;7$SEUvEyOZl}YYr*4|frgFMo|Zx7S1W8l z7KO`VPAZJmTo+=b1*bGOQ_H$NWbtIP_7~6I_0`^%W7+Y6j3X{FW{ddROUTZI{8Py? zzGi-tc~to|i0mkpOQ*Z-z^Bj65K6il7NEq|JBckZxJ$mAb@A}68fxSgaUd9yd2NK&kLk~Ss5Gn=jYO|-G;(}@@1wN0DC9;8J_dWGwP<9Nh$Bo@= zFIi5)hQ0-?Ri2f3>bG#qdG=wcaTK+EiZ-jecmsI816JiYjPr_xlZtgQNq96-j*8EZ zC?qExFIG0KlyO+=oI&xDvLQH8C;zv&J5WFuMU)@z5fza!e|v#~3pv=foZ?a#RONVk zK*=Z^R@|aYipwtz+}0K6f=ih`v6}2pt8$@7iUbS-?ysmXpw<+>i9v7OulZKX#7C~s z^V5MUmox||s*Sg!ebp0ESa?h|sI1jg4LARUN2*3!CtJBep+|rjQ=}KFt2~o0aVr)O zi@jVslG-9mxbMnktoMy<3E6GwY2Cram|GFHir7(soHyL#+urq@E7OCmLpM1)$l^3qs zJLh!i>=EZBc|=Jjq*L=$%^5*5hc}D*&T@F4VD;Yi+Ad}J3DKMsgB?BqhvYVn;DYh( zY=XkVD%dCoF6GYv{!W`4C3+>W2>-C27Lv=->NL$h0_1k+ z_OzEyxD)<<&*FV@iCXVace+2_?x<;G!46cYftRcL>`pZgq;HuW`_8@$a1TL0r-kqN zl8jnYuTqUi+vMJLn}pFT*O@;cC@nCL)l>J&$Hp~Wlg1HmUHge`o2l?}-f?kLoVP~P zm4mS*j_nM0?Y`zQH6DeSx=$io*>EmP2@mBxfqH3HG1Z!zWtJlSXs2r#FM@KI)F?%-9NajX(hZgWdyli;-Ju@bqXj9#Ti5)DNk9hqceaks)=m_FtN+3$2Od#rd~NDR_r)uz|xH$YGo zV#||FKh%FzC0E`_nEx}ena~~0LgBfc$$d*tKg}7D{Nws4lIoPozd0||KIGCSx&*HcmWOU{j zJ5C24awvm}(et#>E03OCCBFrpfYwCZDiwP4Eu8E&Al8`ti_lIMsLV3tihALeKtY&m z@U{@9=y!@Xejv>i9b(OqE8@+1%)fGnU4jQQ66Dki6Y?i53^}MfvSad?ToaESUqi-O z{6fBz^F4z+&J3J>eq~&&)7aI^;(Sjn!9DUUcm;~q{ZKc79G^S=I_LRk%WTVQT_sn( z;~nzYfL$dA;b&dN$Pb#dK4ix>Tc5F}e9qqDUwEnBSSwe!#g``|C zr~EV7oWZofYoeBTAfS+0J6>tZ?uyrhbK8^WwI`RiC-ZCQ zx?uD@s0RAKv#j`qwAU)9n@0r`yA_{w#MJ9_73O3Y)SZ@-)3$&L*>+Q1UKvA{MZ>i1 z|K;ga4?OyMUmfdP|E9vs6lba&^ujt#s_cBkKw)a?p=?ElA(%bPJio$#<_xFp0=v^V z*1*I%Sp&HgZp$c|ZJk&q`;;A, 2005. +# Ricardo Javier Cardenes Medina , 2005. +# AgarFu , 2007. +# Mario Gonzalez , 2007 +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-24 17:08+0000\n" +"PO-Revision-Date: 2007-02-24 18:02-0600\n" +"Last-Translator: Mario Gonzalez \n" +"Language-Team: Castellano \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Lunes" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Martes" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Miércoles" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Jueves" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Viernes" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sábado" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Domingo" + +#: utils/dates.py:14 +msgid "January" +msgstr "Enero" + +#: utils/dates.py:14 +msgid "February" +msgstr "Febrero" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marzo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Mayo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Junio" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Julio" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "Septiembre" + +#: utils/dates.py:15 +msgid "October" +msgstr "Octubre" + +#: utils/dates.py:15 +msgid "November" +msgstr "Noviembre" + +#: utils/dates.py:16 +msgid "December" +msgstr "Diciembre" + +#: utils/dates.py:19 +msgid "jan" +msgstr "ene" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +msgid "may" +msgstr "may" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sep" + +#: utils/dates.py:20 +msgid "oct" +msgstr "oct" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dic" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Ene." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Oct." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dic." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "año" +msgstr[1] "años" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mes" +msgstr[1] "meses" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semana" +msgstr[1] "semanas" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "día" +msgstr[1] "días" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "horas" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minutos" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j N Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j N Y P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "j \\de F" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Árabe" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengalí" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "Catalán" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "Checo" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "Galés" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "Danés" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "Alemán" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "Griego" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "Inglés" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "Español" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "Español Argentino" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "Finés" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "Francés" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "Gallego" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "Húngaro" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "Hebreo" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "Islandés" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "Italiano" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "Japonés" + +#: conf/global_settings.py:58 +msgid "Latvian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Macedonian" +msgstr "Macedonio" + +#: conf/global_settings.py:60 +msgid "Dutch" +msgstr "Alemán" + +#: conf/global_settings.py:61 +msgid "Norwegian" +msgstr "Noruego" + +#: conf/global_settings.py:62 +msgid "Polish" +msgstr "Polaco" + +#: conf/global_settings.py:63 +msgid "Brazilian" +msgstr "Brasileño" + +#: conf/global_settings.py:64 +msgid "Romanian" +msgstr "Rumano" + +#: conf/global_settings.py:65 +msgid "Russian" +msgstr "Ruso" + +#: conf/global_settings.py:66 +msgid "Slovak" +msgstr "Eslovaco" + +#: conf/global_settings.py:67 +msgid "Slovenian" +msgstr "Esloveno" + +#: conf/global_settings.py:68 +msgid "Serbian" +msgstr "Serbio" + +#: conf/global_settings.py:69 +msgid "Swedish" +msgstr "Sueco" + +#: conf/global_settings.py:70 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:71 +msgid "Turkish" +msgstr "Turco" + +#: conf/global_settings.py:72 +msgid "Ukrainian" +msgstr "Ucraniano" + +#: conf/global_settings.py:73 +msgid "Simplified Chinese" +msgstr "Chino simplificado" + +#: conf/global_settings.py:74 +msgid "Traditional Chinese" +msgstr "Chino tradicional" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s de este %(type)s ya existen en este %(field)s." + +#: db/models/manipulators.py:306 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "y" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Por favor, introduzca un %s válido." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Separe múltiples IDs con comas." + +#: db/models/fields/related.py:644 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar más de uno." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Por favor, introduzca IDs de %(self)s válidos. El valor %(value)r no es " +"válido." +msgstr[1] "" +"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son " +"válidos." + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "Ya existe %(optname)s con este %(fieldname)s." + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: newforms/models.py:177 newforms/fields.py:78 newforms/fields.py:374 +#: newforms/fields.py:450 newforms/fields.py:461 oldforms/__init__.py:352 +msgid "This field is required." +msgstr "Este campo es obligatorio." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Este valor debe ser un entero." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Este valor debe ser Verdadero o Falso." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Este campo no puede estar vacío." + +#: db/models/fields/__init__.py:454 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Introduzca una fecha válida en formato AAAA-MM-DD." + +#: db/models/fields/__init__.py:521 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Introduzca una fecha/hora válida en formato AAAA-MM-DD HH:MM." + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "Introduzca un nombre de fichero válido" + +#: template/defaultfilters.py:436 +msgid "yes,no,maybe" +msgstr "si,no,tal vez" + +#: newforms/models.py:164 newforms/fields.py:360 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "Escoja una opción válida. Esa opción no está entre las aceptadas." + +#: newforms/models.py:181 newforms/fields.py:378 newforms/fields.py:454 +msgid "Enter a list of values." +msgstr "Introduzca una lista de valores." + +#: newforms/models.py:187 newforms/fields.py:387 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Escoja una opción válida; '%s' no es una de las opciones disponibles." + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Asegúrese de que su texto tiene a lo más %d caracteres." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Asegúrese de que su texto tiene al menos %d caracteres." + +#: newforms/fields.py:126 core/validators.py:120 +msgid "Enter a whole number." +msgstr "Introduzca un número entero." + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Asegúrese de que este valor es menor o igual a %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Asegúrese de que este valor es mayor o igual a %s." + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "Introduzca una fecha válida." + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "Introduzca una hora válida." + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "Introduzca una fecha/hora válida." + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "Introduzca un valor correcto." + +#: newforms/fields.py:269 core/validators.py:161 +msgid "Enter a valid e-mail address." +msgstr "Introduzca una dirección de correo electrónico válida" + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "Introduzca una URL válida." + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "La URL parece ser un enlace roto." + +#: newforms/widgets.py:170 oldforms/__init__.py:572 +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Desconocido" + +#: newforms/widgets.py:170 oldforms/__init__.py:572 +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Sí" + +#: newforms/widgets.py:170 oldforms/__init__.py:572 +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "No" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Este valor debe contener sólo letras, números y guiones bajos." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Este valor debe contener letras, números, guiones bajos o barras solamente." + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Este valor debe contener sólo letras, números, guiones bajos o medios." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "No se admiten letras mayúsculas." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "No se admiten letras minúsculas." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Introduzca sólo dígitos separados por comas." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Introduzca direcciones de correo válidas separadas por comas." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Por favor introduzca una dirección IP válida." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "No se admiten valores vacíos." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "No se admiten caracteres no numéricos." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Este valor no puede comprender sólo dígitos." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Sólo se admiten caracteres alfabéticos." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "El año debe ser 1900 o posterior." + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Fecha no válida: %s" + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Introduzca una hora válida en formato HH:MM." + +#: core/validators.py:173 core/validators.py:443 oldforms/__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" +"No se ha enviado ningún fichero. Compruebe el tipo de codificación en el " +"formulario." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envíe una imagen válida. El fichero que ha enviado no era una imagen o se " +"trataba de una imagen corrupta." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "La URL %s no apunta a una imagen válida." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Los números de teléfono deben guardar el formato XXX-XXX-XXXX format. \"%s\" " +"no es válido." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "La URL %s no apunta a un vídeo QuickTime válido." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Se precisa una URL válida." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Se precisa HTML válido. Los errores específicos son:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formado: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL no válida: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "La URL %s es un enlace roto." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Introduzca una abreviatura válida de estado de los EEUU." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "¡Cuida tu vocabulario! Aquí no admitimos la palabra %s." +msgstr[1] "¡Cuida tu vocabulario! Aquí no admitimos las palabras %s." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Este campo debe concordar con el campo '%s'." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Por favor, introduzca algo en al menos un campo." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Por favor, rellene ambos campos o deje ambos vacíos." + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Se debe proporcionar este campo si %(field)s es %(value)s" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Se debe proporcionar este campo si %(field)s no es %(value)s" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "No se admiten valores duplicados." + +#: core/validators.py:364 +#, python-format +msgid "This value must be between %(lower)s and %(upper)s." +msgstr "Este valor debe estar entre %(lower)s y %(upper)s." + +#: core/validators.py:367 +#, python-format +msgid "This value must be at least %s." +msgstr "Este valor debe ser como mínimo %s." + +#: core/validators.py:369 +#, python-format +msgid "This value must be no more than %s." +msgstr "Este valor no debe ser mayor que %s." + +#: core/validators.py:405 +#, python-format +msgid "This value must be a power of %s." +msgstr "Este valor debe ser una potencia de %s." + +#: core/validators.py:416 +msgid "Please enter a valid decimal number." +msgstr "Por favor, introduzca un número decimal válido." + +#: core/validators.py:420 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígito en " +"total." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígitos en " +"total." + +#: core/validators.py:423 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígito en " +"total." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígitos en " +"total." + +#: core/validators.py:426 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígito " +"decimal." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con a lo más %s dígitos " +"decimales." + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Asegúrese de que el fichero que envía tiene al menos %s bytes." + +#: core/validators.py:437 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Asegúrese de que el fichero que envía tiene como máximo %s bytes." + +#: core/validators.py:454 +msgid "The format for this field is wrong." +msgstr "El formato de este campo es incorrecto." + +#: core/validators.py:469 +msgid "This field is invalid." +msgstr "Este campo no es válido." + +#: core/validators.py:505 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "No pude obtener nada de %s." + +#: core/validators.py:508 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"La URL %(url)s devolvió la cabecera Content-Type '%(contenttype)s', que no " +"es válida." + +#: core/validators.py:541 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Por favor, cierre la etiqueta %(tag)s de la línea %(line)s. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:545 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Parte del texto que comienza en la línea %(line)s no está permitido en ese " +"contexto. (La línea empieza por \"%(start)s\".)" + +#: core/validators.py:550 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"El \"%(attr)s\" de la línea %(line)s no es un atributo válido. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:555 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"La \"<%(tag)s>\" de la línea %(line)s no es una etiqueta válida. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:559 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"A una etiqueta de la línea %(line)s le faltan uno o más atributos " +"requeridos. (La línea empieza por \"%(start)s\".)" + +#: core/validators.py:564 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"El atributo \"%(attr)s\" de la línea %(line)s tiene un valor que no es " +"válido. (La línea empieza por \"%(start)s\".)" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Asegúrese de que su texto tiene menos de %s carácter." +msgstr[1] "Asegúrese de que su texto tiene menos de %s caracteres." + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "No se permiten saltos de línea." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Escoja una opción válida; '%(data)s' no está en %(choices)s." + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "El fichero enviado está vacío." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Introduzca un número entero entre -32,768 y 32,767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Introduzca un número positivo." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Introduzca un número entero entre 0 y 32,767." + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "nombre de módulo python" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "tipo de contenido" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "tipos de contenido" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:318 +msgid "URL" +msgstr "URL" + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Ejemplo: '/about/contact/'. Asegúrese de que pone barras al principio y al " +"final." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "título" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contenido" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "admitir comentarios" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nombre de plantilla" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Ejemplo: 'flatpages/contact_page.html'. Si no es proporcionado, el sistema usará " +"'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "debe estar registrado" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Si está marcado, sólo los usuarios registrados podrán ver la página." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "página estática" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "páginas estáticas" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Sesión terminada" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nombre" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nombre en código" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permiso" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permisos" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "grupo" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "grupos" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "nombre de usuario" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"Requerido. 30 caracteres o menos. Sólo caracteres alfanuméricos (letras, " +"dígutos y guiones bajos)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "nombre" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "apellidos" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "dirección de correo" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "clave" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"Use'[algo]$[sal]$[hash hexadecimal]' o use el " +"formulario para cambiar la contraseña." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "es staff" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Indica si el usuario puede entrar en este sitio de administración." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "activo" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Indica si el usuario puede entrar en este sitio de administración. Desmarque " +"esto en lugar de borrar la cuenta." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "es superusuario" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"Indica que este usuario tiene todos los permisos sin asignárselos " +"explícitamente." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "Último registro" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "fecha de creación" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Además de los permisos asignados manualmente, este usuario también tendrá " +"todos los permisos de los grupos en los que esté." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "permisos" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "usuario" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "usuarios" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Información personal" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Permisos" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Fechas importantes" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Grupos" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "mensaje" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "Las dos contraseñas no coinciden." + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "Ya existe un usuario con este nombre." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Tu navegador de internet parece no tener las cookies habilitadas. Las " +"cookies se necesitan para poder ingresar." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Por favor, introduzca un correcto nombre de usuario y contraseña. Note que " +"ambos campos son sensibles a mayúsculas/minúsculas." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "Esta cuenta está inactiva." + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" +"Esta dirección de correo electrónico no tiene una cuenta de usuario " +"asociada. ¿Está seguro de que se ha registrado?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "Las contraseñas introducidas en los campos 'nueva contraseña' no coinciden." + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "" +"Tu contraseña antígua es incorrecta. Por favor, vuelve a introducirla " +"correctamente." + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID de objeto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "encabezado" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentario" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "calificación 1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "calificación 2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "calificación 3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "calificación 4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "calificación 5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "calificación 6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "calificación 7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "calificación 8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "es calificación válida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "fecha/hora de envío" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "es público" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:307 +msgid "IP address" +msgstr "Dirección IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "está eliminado" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Marque esta caja si el comentario es inapropiado. En su lugar se mostrará " +"\"Este comentario ha sido eliminado\"." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "comentarios" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Objeto contenido" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Enviado por %(user)s en %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nombre de la persona" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "dirección ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprobado por el staff" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "comentario libre" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "comentarios libres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "puntuación" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "fecha de la puntuación" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "punto karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "puntos karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "puntuado %(score)d por %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentario fue marcado por %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "fecha de la marca" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "marca de usuario" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "marcas de usuario" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Marca de %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "fecha de eliminación" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "eliminación de moderador" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "eliminaciones de moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Eliminación del moderador %r" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Tu nombre:" + +#: contrib/comments/templates/comments/freeform.html:5 +#: contrib/comments/templates/comments/form.html:28 +msgid "Comment:" +msgstr "Comentario:" + +#: contrib/comments/templates/comments/freeform.html:10 +#: contrib/comments/templates/comments/form.html:35 +msgid "Preview comment" +msgstr "Previsualizar comentario" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Usuario:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "Terminar sesión" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Clave:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "¿Has olvidado tu contraseña?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Calificaciones" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requerido" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Postea una fotografía" + +#: contrib/comments/views/comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "Se precisa esta puntuación porque ha introducido al menos otra más." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Este comentario lo envió un usuario que ha enviado menos de %(count)s " +"comentario:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Este comentario lo envió un usuario que ha enviado menos de %(count)s " +"comentarios:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentario ha sido colocado por un usuario poco preciso: \n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Sólo se admite POST" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "No se proporcionó uno o más de los siguientes campos requeridos" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" +"Alguien está jugando con el formulario de comentarios (violación de " +"seguridad)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"El formulario de comentarios tiene un parámetro 'target' no válido (el ID de " +"objeto era inválido)" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "El formulario de comentario no proporcionó 'previsualizar' ni 'enviar'" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Los usuarios anónimos no pueden votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de comentario no válido" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "No puedes votarte tú mismo" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirigir desde" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Esta ruta debería ser absoluta, excluyendo el nombre de dominio. Ejeplo: '/" +"events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirigir a" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Esto puede ser bien una ruta absoluta (como antes) o una URL completa que " +"empiece con 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redirección" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redirecciones" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nombre de dominio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nombre para mostrar" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "sitio" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sitios" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                By %s:

                                \n" +"
                                  \n" +msgstr "" +"

                                  Por %s:

                                  \n" +"
                                    \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Todo" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Cualquier fecha" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Hoy" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últimos 7 días" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Este mes" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Este año" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "hora de acción" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id de objeto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr de objeto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "marca de acción" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "mensaje de cambio" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada de registro" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entradas de registro" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Actualmente:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Fecha:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora:" + +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Cambiar clave" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Home" +msgstr "Inicio" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Documentación" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Bookmarklets de documentación" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                    To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                    \n" +msgstr "" +"\n" +"

                                    Para instalar bookmarklets, arrastre el enlace a su barra\n" +"de favoritos, o pulse con el botón derecho el enlace y añádalo a sus " +"favoritos.\n" +"Ahora puede escoger el bookmarklet desde cualquier página en el sitio.\n" +"Observer que algunos de estos bookmarklets precisan que esté viendo\n" +"el sitio desde un computador señalado como \"interno\" (hable\n" +"con su administrador de sistemas si no está seguro de si el suyo lo es).\n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentación de esta página" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "Le lleva desde cualquier página a la documentación de la vista que la genera." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostrar ID de objeto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Muestra el tipo de contenido e ID unívoco de las páginas que representan un " +"único objeto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar este objeto (ventana actual)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Le lleva a la página de administración de páginas que representan un único " +"objeto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar este objeto (nueva ventana)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Como antes, pero abre la página de administración en una nueva ventana." + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Error del servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Error del servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Error de servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Ha ocurrido un error. Se ha informado a los administradores del sitio " +"mediante correo electrónico y debería arreglarse en breve. Gracias por su " +"paciencia" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Buscar" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 resultado" +msgstr[1] "%(counter)s resultados" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s total" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Histórico" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Fecha/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Usuario" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Acción" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j M Y P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Este objeto no tiene histórico de cambios. Probablemente no fue añadido " +"usando este sitio de administración." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Sitio de administración de Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administración de Django" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Bienvenido," + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "Identificarse" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Agregar %(name)s" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Eliminar" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Eliminar el %(object_name)s '%(escaped_object)s' provocaría la eliminación " +"de objetos relacionados, pero su cuenta no tiene permiso para borrar los " +"siguientes tipos de objetos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"¿Está seguro de que quiere borrar los %(object_name)s \"%(escaped_object)s" +"\"? Se borrarán los siguientes objetos relacionados:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Sí, estoy seguro" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Página no encontrada" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Lo sentimos, pero no se encuentra la página solicitada." + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtro" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Grabar como nuevo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Grabar y añadir otro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Grabar y continuar editando" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Grabar" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelos disponibles en la aplicación %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Agregar" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "No tiene permiso para editar nada." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Acciones recientes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mis acciones" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ninguno disponible" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Mostrarlo todo" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Ver en el sitio" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Por favor, corrija el siguiente error." +msgstr[1] "Por favor, corrija los siguientes errores." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Ordenación" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Orden:" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"Algo va mal con la instalación de la base de datos. Asegúrate que las tablas " +"necesarias han sido creadas, y que la base de datos puede ser leida por el " +"usuario apropiado." + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Por %(filter_title)s " + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Primero, introduzca un nombre de usuario y una contraseña. Luego, podrá " +"editar el resto de opciones del usuario." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Nombre de usuario" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +msgid "Password" +msgstr "Contraseña" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +msgid "Password (again)" +msgstr "Contraseña (de nuevo)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "Introduzca la misma contraseña que arriba, para verificación" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" +"Introduzca una nueva contraseña para el usuario %(username)s." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Recuperar clave" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"¿Ha olvidado su clave? Introduzca su dirección de correo electrónico, y " +"crearemos una nueva que le enviaremos por correo." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Dirección de correo electrónico:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Recuperar mi clave" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Gracias por el tiempo que ha dedicado al sitio web hoy." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Identificarse de nuevo" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Está recibiendo este mensaje debido a que solicitó recuperar la clave" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "de su cuenta de usuario en %(site_name)s." + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Su nueva clave es: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Puede cambiarla accediendo a esta página:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Su nombre de usuario, en caso de haberlo olvidado:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "¡Gracias por usar nuestro sitio!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "El equipo de %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Recuperación de clave exitosa" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Le hemos enviado una clave nueva a la dirección que ha suministrado. Debería " +"recibirla en breve." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Cambio de clave" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Por favor, introduzca su clave antigua, por seguridad, y después introduzca " +"la nueva clave dos veces para verificar que la ha escrito correctamente." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Clave antigua:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Clave nueva:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirme clave:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Cambiar mi clave" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Cambio de clave exitoso" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Su clave ha sido cambiada." + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "Todas las fechas" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Por favor, identifíquese de nuevo, porque su sesión ha caducado. No se " +"preocupe: se ha guardado su envío." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Parece que su navegador no está configurado para aceptar cookies. Actívelas " +"por favor, recargue esta página, e inténtelo de nuevo." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Los nombres de usuario no pueden contener el carácter '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su " +"lugar." + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "etiqueta:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filtro:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "vista:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "Applicación %r no encontrada" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %(model_name)r not found in app %(app_label)r" +msgstr "El modelo %(model_name)s no se ha encontrado en la aplicación %(app_label)r" + +#: contrib/admin/views/doc.py:184 +#, python-format +msgid "the related `%(app_label)s.%(data_type)s` object" +msgstr "el objeto relacionado`%(app_label)s.%(data_type)s` " + +#: contrib/admin/views/doc.py:185 contrib/admin/views/doc.py:207 +#: contrib/admin/views/doc.py:222 contrib/admin/views/doc.py:227 +msgid "model:" +msgstr "modelo:" + +#: contrib/admin/views/doc.py:216 +#, python-format +msgid "related `%(app_label)s.%(object_name)s` objects" +msgstr "los objetos relacionados `%(app_label)s.%(object_name)s`" + +#: contrib/admin/views/doc.py:222 +#, python-format +msgid "all %s" +msgstr "todo %s" + +#: contrib/admin/views/doc.py:227 +#, python-format +msgid "number of %s" +msgstr "número de %s" + +#: contrib/admin/views/doc.py:232 +#, python-format +msgid "Fields on %s objects" +msgstr "Campos en %s objetos" + +#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:304 +#: contrib/admin/views/doc.py:306 contrib/admin/views/doc.py:312 +#: contrib/admin/views/doc.py:313 contrib/admin/views/doc.py:315 +msgid "Integer" +msgstr "Entero" + +#: contrib/admin/views/doc.py:295 +msgid "Boolean (Either True or False)" +msgstr "Booleano (Verdadero o Falso)" + +#: contrib/admin/views/doc.py:296 contrib/admin/views/doc.py:314 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Cadena (máximo %(maxlength)s)" + +#: contrib/admin/views/doc.py:297 +msgid "Comma-separated integers" +msgstr "Enteros separados por comas" + +#: contrib/admin/views/doc.py:298 +msgid "Date (without time)" +msgstr "Fecha (sin hora)" + +#: contrib/admin/views/doc.py:299 +msgid "Date (with time)" +msgstr "Fecha (con hora)" + +#: contrib/admin/views/doc.py:300 +msgid "E-mail address" +msgstr "Dirección de correo electrónico" + +#: contrib/admin/views/doc.py:301 contrib/admin/views/doc.py:302 +#: contrib/admin/views/doc.py:305 +msgid "File path" +msgstr "Ruta de fichero" + +#: contrib/admin/views/doc.py:303 +msgid "Decimal number" +msgstr "Número decimal" + +#: contrib/admin/views/doc.py:309 +msgid "Boolean (Either True, False or None)" +msgstr "Booleano (Verdadero, Falso o Nulo)" + +#: contrib/admin/views/doc.py:310 +msgid "Relation to parent model" +msgstr "Relación con el modelo padre" + +#: contrib/admin/views/doc.py:311 +msgid "Phone number" +msgstr "Número de teléfono" + +#: contrib/admin/views/doc.py:316 +msgid "Text" +msgstr "Texto" + +#: contrib/admin/views/doc.py:317 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:319 +msgid "U.S. state (two uppercase letters)" +msgstr "Estado de los EEUU (dos letras mayúsculas)" + +#: contrib/admin/views/doc.py:320 +msgid "XML text" +msgstr "Texto XML" + +#: contrib/admin/views/doc.py:346 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s no parece ser un objeto urlpattern" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Sitio administrativo" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "Se añadió con éxito el %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "Puede editarlo de nuevo abajo." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Puede agregar otro %s abajo." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Agregar %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Agregado %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Modificado %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Borrado %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "No ha cambiado ningún campo." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "Se modificó con éxito el %(name)s \"%(obj)s." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "Se agregó con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Uno o más %(fieldname)s en %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Uno o más %(fieldname)s en %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "¿Está seguro?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Modificar histórico: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Escoja %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Escoja %s para modificar" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Erorr en la base de datos" + +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "Añadir usuario" + +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." +msgstr "La clave se ha cambiado exitosamente." + +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "Cambiar clave: %s" + +#: contrib/localflavor/usa/forms.py:17 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "Introduzca un código zip en el formato XXXXX o XXXX-XXXX." + +#: contrib/localflavor/uk/forms.py:18 +msgid "Enter a postcode. A space is required between the two postcode parts." +msgstr "" +"Introduzca in código postal. Se necesita un espacio entre las dos partes del " +"código." + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "clave de sesión" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "datos de sesión" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "fecha de caducidad" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sesión" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sesiones" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "El %(verbose_name)s se ha creado correctamente." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "Se actualizó con éxito el %(verbose_name)s." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "El %(verbose_name)s ha sido eliminado." + diff --git a/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..701443cace2d5c2ef15e0f29d58db22635be24b8 GIT binary patch literal 1427 zcwTLj&u<$=6vqe0rmcZe%C8HcmqR5`*q*u#*37KI zz4p)>aOr^q5)ya*3hw*|h~7Yg14kqdd}n7xgv3ZQpXYh=e(clNKVNM=6j;w=zl8lG z_RHACW1?EWfRBTZz%}q!@JaA@@FMtE?Qb-McnZ7(UIJgNxem6#>)=OVw-MtWfKBl0 zM$G%L5%YXg=lKpK@CR@k{He}!aV^GQUaRYXgdWz{V*D*6y#Vfkm%$IgHuz~X#+}vo z9()44-;DLXYR3HEf**t5*KvQ>aTi+Af29@kztD)>19VJm*m{Z{PfK`Vae zmtYuau%8p>E6lwm{<}hdPm7o*)P6?9nTHi>V-!{IA%E!MS?sH|Kg^_E+gHMIgXG41 zL-eyu^p{cz_TIDk@ z4vo<`J{QM4$15?W5l!eW_AZUZm#w798HoP%hDq>8A#7P+U#_;lI~{Gxk=q_ zZ>xK~)9rNdFgfO>a{u%025okGZ*InS2h#auVx@M4^v3pR-`Je#19@sRy^{yLU!5Dc zf3H2-8x7A}4~^~^?O7woYJno2zwmD?3aRwln5ndm{kwbPqhxz~>t@nDe;f*a#x@!1 z)MQHMJ=&fs-~Lx%x#b*ky5?x0+`?!Dp`FW2nObOgGAtDOEw!YXu`)yq3&zll3$D3> z!<+EymYIT|qa{Y1 zDXC35i>Ar`kfL9;6vb!7fDWL)5$)3n)OTCnBVRfSB1|z&cC?~Q*4o40MGOqOhcXVz o;6$rFK>cP@gJGaaUIHhIZSapDO=V`pZL<<1`KQ#VzCzJ|01|dpqW}N^ literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..99856ce --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/es/LC_MESSAGES/djangojs.po @@ -0,0 +1,110 @@ +# Spanish translation for the django-admin JS files. +# Copyright (C) +# This file is distributed under the same license as the PACKAGE package. +# Jorge Gajon , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: Django JavaScript 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2005-12-06 21:32+0100\n" +"Last-Translator: Jorge Gajon \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s Disponibles" + +#: contrib/admin/media/js/SelectFilter2.js:41 +#, fuzzy +msgid "Choose all" +msgstr "Selecciona todos" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Agregar" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Remover" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s Elegidos" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Haz tus elecciones y da click en " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Elimina todos" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Enero Febrero Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre " +"Noviembre Diciembre" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Domingo Lunes Martes Miércoles Jueves Viernes Sábado" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M M J V S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Ahora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Reloj" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Elige una hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Medianoche" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 a.m." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Mediodía" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Cancelar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Hoy" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendario" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Ayer" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Mañana" diff --git a/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..b7f777611b1d17eedf5809d93b7ef03695339b51 GIT binary patch literal 40281 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 +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-25 17:21-0300\n" +"PO-Revision-Date: 2007-02-25 17:46-0300\n" +"Last-Translator: Ramiro Morales \n" +"Language-Team: Spanish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "nombre de la clase python del modelo" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "tipo de contenido" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "tipos de contenido" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Sesión cerrada" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nombre" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nombre en código" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permiso" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permisos" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "grupo" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "grupos" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "nombre de usuario" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"Requerido. Longitud máxima 30 caracteres alfanuméricos (letras, dígitos y " +"guiones bajos)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "nombre" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "apellido" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "dirección de correo" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "contraseña" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"Use '[algo]$[salt]$[hexdigest]' o use el formulario de " +"cambio de contraseña." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "es staff" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Indica si el usuario puede ingresar a este sitio de administración." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "activo" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Indica si el usuario puede ingresar al sitio de administración Django." +"Desactive este campo en lugar de eliminar usuarios." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "es superusuario" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"Indica que este usuario posee todos los permisos, sin asignarle los mismos " +"explícitamente." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "último registro" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "fecha de creación" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Además de los permisos asignados manualmente, este usuario también poseerá " +"todos los permisos de los grupos a los que pertenezca." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "permisos de usuario" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "usuario" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "usuarios" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Información personal" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Permisos" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Fechas importantes" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Grupos" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "mensaje" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "Los dos campos de contraseñas no coinciden entre si." + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "Ya existe un usuario con ese nombre." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Su navegador Web aparenta no tener cookies activas. Las cookies son un " +"requerimiento para poder ingresar." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Por favor introduzca un nombre de usuario y una contraseña correctos. Note " +"que ambos campos son sensibles a mayúsculas/minúsculas." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "Esta cuenta está inactiva" + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" +"Esa dirección de e-mail no está asociada a ninguna cuenta de usuario. ¿Está " +"seguro de que ya se ha registrado?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "Los dos campos 'nueva contraseña' no coinciden entre si." + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "" +"La antigua contraseña ingresada es incorrecta. Por favor ingrésela " +"nuevamente." + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirigir desde" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Esta ruta debería ser absoluta, excluyendo el nombre de dominio. Ejemplo: '/" +"events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirigir a" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Esto puede ser bien una ruta absoluta (como antes) o una URL completa que " +"empiece con 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redirección" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redirecciones" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID de objeto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "encabezado" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentario" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "calificación 1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "calificación 2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "calificación 3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "calificación 4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "calificación 5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "calificación 6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "calificación 7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "calificación 8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "es calificación válida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "fecha/hora de envío" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "es público" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "Dirección IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "está eliminado" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Marque esta caja si el comentario es inapropiado. En su lugar se mostrará " +"\"Este comentario ha sido eliminado\"." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "comentarios" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Objeto contenido" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Enviado por %(user)s el %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nombre de la persona" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "dirección ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprobado por el staff" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "comentario libre" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "comentarios libres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "puntuación" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "fecha de la puntuación" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "punto karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "puntos karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "puntuado %(score)d por %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentario fue marcado por %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "fecha de la marca" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "marca de usuario" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "marcas de usuario" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Marca de %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "fecha de eliminación" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "Eliminación por moderador" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "eliminaciones por moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Eliminación del moderador %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Los usuarios anónimos no pueden votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de comentario no válido" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "No puedes votarte tú mismo" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Se precisa esta puntuación porque ha introducido al menos otra más." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Este comentario lo envió un usuario que ha enviado menos de %(count)s " +"comentario:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Este comentario lo envió un usuario que ha enviado menos de %(count)s " +"comentarios:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentario ha sido enviado por un usuario 'semi-anónimo':\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Sólo se admiten POSTs" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "No se proporcionó uno o más de los siguientes campos requeridos" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" +"Alguien está jugando con el formulario de comentarios (violación de " +"seguridad)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"El formulario de comentarios tiene un parámetro 'target' no válido (el ID de " +"objeto era inválido)" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "El formulario de comentario no proporcionó 'previsualizar' ni 'enviar'" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Su nombre:" + +#: contrib/comments/templates/comments/freeform.html:5 +#: contrib/comments/templates/comments/form.html:28 +msgid "Comment:" +msgstr "Comentario:" + +#: contrib/comments/templates/comments/freeform.html:10 +#: contrib/comments/templates/comments/form.html:35 +msgid "Preview comment" +msgstr "Previsualizar comentario" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Usuario:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Cerrar sesión" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Contraseña:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Olvidó su contraseña?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Calificaciones" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requerido" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Enviar una foto" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:315 +msgid "URL" +msgstr "URL" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Ejemplo: '/about/contact/'. Asegúrese de que pone barras al principio y al " +"final." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "título" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contenido" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "activar comentarios" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nombre de plantilla" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Ejemplo: 'flatpages/contact_page.html'. Si no lo proporciona, el sistema " +"usará 'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "debe estar registrado" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Si está marcado, sólo los usuarios registrados podrán ver la página." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "página estática" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "páginas estáticas" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "clave de sesión" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "datos de sesión" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "fecha de caducidad" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sesión" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sesiones" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nombre de dominio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nombre para visualizar" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "sitio" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sitios" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                    By %s:

                                    \n" +"
                                      \n" +msgstr "" +"

                                      Por %s:

                                      \n" +"
                                        \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Todos/as" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Cualquier fecha" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Hoy" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últimos 7 días" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Este mes" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Este año" + +#: contrib/admin/filterspecs.py:143 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "Yes" +msgstr "Sí" + +#: contrib/admin/filterspecs.py:143 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "No" +msgstr "No" + +#: contrib/admin/filterspecs.py:150 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "Unknown" +msgstr "Desconocido" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "hora de acción" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id de objeto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr de objeto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "marca de acción" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "mensaje de cambio" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada de registro" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entradas de registro" + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "Todas las fechas" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Identificarse" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Por favor, identifíquese de nuevo porque su sesión ha caducado. No se " +"preocupe: se ha guardado su envío." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Parece que su navegador no está configurado para aceptar cookies. Actívelas " +"por favor, recargue esta página, e inténtelo de nuevo." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Los nombres de usuario no pueden contener el carácter '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su " +"lugar." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Sitio administrativo" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "Se agregó con éxito %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "Puede modificarlo nuevamente abajo." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Puede agregar otro %s abajo." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Agregar %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Agregado %s." + +#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 db/models/manipulators.py:306 +msgid "and" +msgstr "y" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Modifica %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Elimina %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "No ha modificado ningún campo." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "Se modificó con éxito %(name)s \"%(obj)s." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"Se agregó con éxito %(name)s \"%(obj)s. Puede modificarlo nuevamente abajo." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Uno o más %(fieldname)s en %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Uno o más %(fieldname)s en %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "Se eliminó con éxito %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "¿Está seguro?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Historia de modificaciones: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Seleccione %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Seleccione %s a modificar" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Error de base de datos" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "etiqueta:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "Filtrar:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "ver:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "App %r no encontrada" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Modelo %r no encontrado en app %r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "El objeto relacionado `%s.%s`" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "modelo:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "objetos relacionados `%s.%s`" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "todos %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "número de %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Campos en %s objetos" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Entero" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Booleano (Verdadero o Falso)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Cadena (máximo %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Enteros separados por comas" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Fecha (sin hora)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Fecha (con hora)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Dirección de correo electrónico" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Ruta de archivo" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Número decimal" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Booleano (Verdadero, Falso o Nulo)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relación con el modelo padre" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Número de teléfono" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Texto" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Estado de los EEUU (dos letras mayúsculas)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "Texto XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s no parece ser un objeto urlpattern" + +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "Agregar usuario" + +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." +msgstr "Cambio de contraseña exitoso" + +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "Cambiar contraseña: %S" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Actualmente" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Fecha:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora:" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Documentación" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Cambiar contraseña" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Inicio" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Historia" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Fecha/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Usuario" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Acción" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j M Y P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Este objeto no tiene historia de modificaciones. Probablemente no fue " +"añadido usando este sitio de administración." + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Agregar %(name)s" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Por %(filter_title)s " + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Error del servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Error del servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Error de servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Ha ocurrido un error. Se ha informado a los administradores del sitio " +"mediante correo electrónico y debería arreglarse en breve. Gracias por su " +"paciencia" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Buscar" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "un resultado" +msgstr[1] "%(counter)s resultados" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "total: %(full_result_count)s" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Mostrar todos/as" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Sitio de administración de Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administración de Django" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelos disponibles en la aplicación %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Agregar" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "No tiene permiso para editar nada." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Acciones recientes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mis acciones" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ninguna disponible" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Página no encontrada" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Lo sentimos, pero no se encuentra la página solicitada." + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtrar" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Ver en el sitio" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Por favor, corrija el siguiente error." +msgstr[1] "Por favor, corrija los siguientes errores." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Ordenación" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Orden:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Bienvenido," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Eliminar" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Eliminar el %(object_name)s '%(escaped_object)s' provocaría la eliminación " +"de objetos relacionados, pero su cuenta no tiene permiso para eliminar los " +"siguientes tipos de objetos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"¿Está seguro de que quiere eliminar los %(object_name)s \"%(escaped_object)s" +"\"? Se eliminarán los siguientes objetos relacionados:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Sí, estoy seguro" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Grabar como nuevo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Grabar y añadir otro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Grabar y continuar editando" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Grabar" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"hay algún problema con su instalación de base de datos. Asegúrese de que las " +"tablas de la misma hayan sido creadas, y asegúrese de que el usuario " +"apropiado tenga permisos de escritura en la base de datos." + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" +"Introduzca una nueva contraseªna para el usuario %(username)s." + +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Contraseña:" + +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Contraseña (de nuevo)" + +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Para verificación, introduzca la misma contraseña que ingresó arriba." + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Primero, introduzca un nombre de usuario y una contraseña. Luego podrá " +"configurar opciones adicionales." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Nombre de usuario:" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Cambio de contraseña" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Cambio de contraseña exitoso" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Su contraseña ha sido cambiada." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Recuperar contraseña" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"¿Ha olvidado su contraseña? Introduzca su dirección de correo electrónico, y " +"crearemos una nueva que le enviaremos por correo." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Dirección de correo electrónico:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Recuperar mi cöntraseña" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Gracias por el tiempo que ha dedicado al sitio web hoy." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Identificarse de nuevo" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Recuperación de contraseña exitosa" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Le hemos enviado una nueva contraseña a la dirección que ha suministrado. " +"Debería recibirla en breve." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Por favor, introduzca su contraseña antigua, por seguridad, y después " +"introduzca la nueva contraseña dos veces para verificar que la ha escrito " +"correctamente." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Contraseña antigua:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Contraseña nueva:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirme contraseña:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Cambiar mi contraseña" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Está recibiendo este mensaje debido a que solicitó recuperar la contraseña" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "de su cuenta de usuario en %(site_name)s." + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Su nueva contraseña es: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Puede cambiarla accediendo a esta página:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Su nombre de usuario, en caso de haberlo olvidado:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "¡Gracias por usar nuestro sitio!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "El equipo de %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Bookmarklets de documentación" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                        To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                        \n" +msgstr "" +"\n" +"

                                        Para instalar bookmarklets, arrastre el enlace a su barra\n" +"de favoritos, o pulse con el botón derecho el enlace y añádalo a sus " +"favoritos.\n" +"Ahora puede sleccionar el bookmarklet desde cualquier página en el sitio.\n" +"Observer que algunos de estos bookmarklets precisan que esté viendo\n" +"el sitio desde un equipo señalado como \"interno\" (hable\n" +"con su administrador de sistemas si no está seguro de si el suyo lo es).\n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentación de esta página" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Le lleva desde cualquier página a la documentación de la vista que la genera." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostrar ID de objeto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Muestra el tipo de contenido e ID unívoco de las páginas que representan un " +"único objeto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar este objeto (ventana actual)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Le lleva a la página de administración de páginas que representan un único " +"objeto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar este objeto (nueva ventana)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" +"Como antes, pero abre la página de administración en una nueva ventana." + +#: contrib/localflavor/uk/forms.py:18 +msgid "Enter a postcode. A space is required between the two postcode parts." +msgstr "" +"Introduzca un postcode. Se requiere un espacio entre ambas partes del " +"postcode." + +#: contrib/localflavor/usa/forms.py:17 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "Introduzca un zip code en el formato XXXXX o XXXXX-XXXX." + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Lunes" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Martes" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Miércoles" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Jueves" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Viernes" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sábado" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Domingo" + +#: utils/dates.py:14 +msgid "January" +msgstr "Enero" + +#: utils/dates.py:14 +msgid "February" +msgstr "Febrero" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marzo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Mayo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Junio" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Julio" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "Setiembre" + +#: utils/dates.py:15 +msgid "October" +msgstr "Octubre" + +#: utils/dates.py:15 +msgid "November" +msgstr "Noviembre" + +#: utils/dates.py:16 +msgid "December" +msgstr "Diciembre" + +#: utils/dates.py:19 +msgid "jan" +msgstr "ene" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +msgid "may" +msgstr "may" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "oct" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dic" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Enero" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Oct." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dic." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "año" +msgstr[1] "años" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mes" +msgstr[1] "meses" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semana" +msgstr[1] "semanas" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "día" +msgstr[1] "días" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "horas" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minutos" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j N Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j N Y P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "j \\de F" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Árabe" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengalí" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "Catalán" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "Checo" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "Galés" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "Danés" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "Alemán" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "Griego" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "Inglés" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "Español" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "Español Argentino" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "Finlandés" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "Francés" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "Gallego" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "Húngaro" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "Hebreo" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "Islandés" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "Italiano" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "Japonés" + +#: conf/global_settings.py:58 +msgid "Latvian" +msgstr "Latvio" + +#: conf/global_settings.py:59 +msgid "Macedonian" +msgstr "Macedonio" + +#: conf/global_settings.py:60 +msgid "Dutch" +msgstr "Holandés" + +#: conf/global_settings.py:61 +msgid "Norwegian" +msgstr "Noruego" + +#: conf/global_settings.py:62 +msgid "Polish" +msgstr "Polaco" + +#: conf/global_settings.py:63 +msgid "Brazilian" +msgstr "Brasileño" + +#: conf/global_settings.py:64 +msgid "Romanian" +msgstr "Rumano" + +#: conf/global_settings.py:65 +msgid "Russian" +msgstr "Ruso" + +#: conf/global_settings.py:66 +msgid "Slovak" +msgstr "Eslovaco" + +#: conf/global_settings.py:67 +msgid "Slovenian" +msgstr "Esloveno" + +#: conf/global_settings.py:68 +msgid "Serbian" +msgstr "Serbio" + +#: conf/global_settings.py:69 +msgid "Swedish" +msgstr "Sueco" + +#: conf/global_settings.py:70 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:71 +msgid "Turkish" +msgstr "Turco" + +#: conf/global_settings.py:72 +msgid "Ukrainian" +msgstr "Ucraniano" + +#: conf/global_settings.py:73 +msgid "Simplified Chinese" +msgstr "Chino simplificado" + +#: conf/global_settings.py:74 +msgid "Traditional Chinese" +msgstr "Chino tradicional" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "Ya existe un(a) %(object)s con este/a %(type)s para %(field)s." + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "Ya existe %(optname)s con este %(fieldname)s." + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: newforms/fields.py:78 newforms/fields.py:374 newforms/fields.py:450 +#: newforms/fields.py:461 newforms/models.py:177 oldforms/__init__.py:352 +msgid "This field is required." +msgstr "Este campo es obligatorio." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Este valor debe ser un número entero." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Este valor debe ser True o False." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Este campo no puede ser nulo." + +#: db/models/fields/__init__.py:454 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Introduzca una fecha válida en formato AAAA-MM-DD." + +#: db/models/fields/__init__.py:521 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Introduzca una fecha/hora válida en formato YYYY-MM-DD HH:MM." + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "Introduzca un nombre de achivo válido." + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Por favor, introduzca un %s válido." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr " Separe múltiples IDs con comas." + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Mantenga presionada \"Control\" (\"Command\" en un Mac) para seleccionar más " +"de uno." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Por favor, introduzca IDs de %(self)s válidos. El valor %(value)r no es " +"válido." +msgstr[1] "" +"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son " +"válidos." + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Este valor debe contener sólo letras, números y guiones bajos." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Este valor debe contener sólo letras, números, guiones bajos, guiones o " +"barras (/)" + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"Este valor debe contener sólo letras, números, guiones bajos o guiones." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "No se admiten letras mayúsculas." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "No se admiten letras minúsculas." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Introduzca sólo dígitos separados por comas." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Introduzca direcciones de correo válidas separadas por comas." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Por favor introduzca una dirección IP válida." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "No se admiten valores vacíos." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "No se admiten caracteres no numéricos." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Este valor no puede estar formado sólo por dígitos." + +#: core/validators.py:120 newforms/fields.py:126 +msgid "Enter a whole number." +msgstr "Introduzca un número entero." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Sólo se admiten caracteres alfabéticos." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "El año debe ser 1900 o posterior." + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Fecha no válida: %s." + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Introduzca una hora válida en formato HH:MM." + +#: core/validators.py:161 newforms/fields.py:269 +msgid "Enter a valid e-mail address." +msgstr "Introduzca una dirección de correo electrónico válida" + +#: core/validators.py:173 core/validators.py:444 oldforms/__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" +"No se envió un archivo. Verifique el tipo de codificación en el formulario." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envíe una imagen válida. El archivo que ha enviado no era una imagen o se " +"trataba de una imagen corrupta." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "La URL %s no apunta a una imagen válida." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Los números telefónicos deben respetar el formato XXX-XXX-XXXX. \"%s\" no es " +"válido." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "La URL %s no apunta a un vídeo QuickTime válido." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Se precisa una URL válida." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Se precisa HTML válido. Los errores específicos son:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formado: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL no válida: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "La URL %s es un enlace roto." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Introduzca una abreviatura válida de estado de los EEUU." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "¡Vigila tu boca! Aquí no admitimos la palabra %s." +msgstr[1] "¡Vigila tu boca! Aquí no admitimos las palabras %s." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Este campo debe concordar con el campo '%s'." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Por favor, introduzca algo en al menos un campo." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Por favor, rellene ambos campos o deje ambos vacíos." + +#: core/validators.py:319 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Se debe proporcionar este campo si %(field)s es %(value)s" + +#: core/validators.py:332 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Se debe proporcionar este campo si %(field)s no es %(value)s" + +#: core/validators.py:351 +msgid "Duplicate values are not allowed." +msgstr "No se admiten valores duplicados." + +#: core/validators.py:366 +#, python-format +msgid "This value must be between %s and %s." +msgstr "Este valor debe ser estar entre %s y %s." + +#: core/validators.py:368 +#, python-format +msgid "This value must be at least %s." +msgstr "Este valor debe ser al menos %s." + +#: core/validators.py:370 +#, python-format +msgid "This value must be no more than %s." +msgstr "Este valor debe ser no mayor que %s." + +#: core/validators.py:406 +#, python-format +msgid "This value must be a power of %s." +msgstr "Este valor debe ser una potencia de %s." + +#: core/validators.py:417 +msgid "Please enter a valid decimal number." +msgstr "Por favor, introduzca un número decimal válido." + +#: core/validators.py:421 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con con un máximo de un " +"dígito en total." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con un maximo de %s dígitos " +"en total." + +#: core/validators.py:424 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con un dígito entero como " +"máximo." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con un máximo de %s dígitos " +"enteros." + +#: core/validators.py:427 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Por favor, introduzca un número decimal válido con un máximo de una posición " +"decimal." +msgstr[1] "" +"Por favor, introduzca un número decimal válido con un máximo de %s " +"posiciones decimales." + +#: core/validators.py:437 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Asegúrese de que el archivo que envía es de un tamaño mínimo de " +"%s bytes." + +#: core/validators.py:438 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Asegúrese de que el archivo que envía es de un tamaño máximo de " +"%s bytes." + +#: core/validators.py:455 +msgid "The format for this field is wrong." +msgstr "El formato de este campo es incorrecto." + +#: core/validators.py:470 +msgid "This field is invalid." +msgstr "Este campo no es válido." + +#: core/validators.py:506 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "No pude obtener nada de %s." + +#: core/validators.py:509 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"La URL %(url)s devolvió la cabecera Content-Type '%(contenttype)s', que no " +"es válida." + +#: core/validators.py:542 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Por favor, cierre la etiqueta %(tag)s de la línea %(line)s. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:546 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Parte del texto que comienza en la línea %(line)s no está permitido en ese " +"contexto. (La línea empieza por \"%(start)s\".)" + +#: core/validators.py:551 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"El \"%(attr)s\" de la línea %(line)s no es un atributo válido. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:556 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"La \"<%(tag)s>\" de la línea %(line)s no es una etiqueta válida. (La línea " +"empieza por \"%(start)s\".)" + +#: core/validators.py:560 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"A una etiqueta de la línea %(line)s le faltan uno o más atributos " +"requeridos. (La línea empieza por \"%(start)s\".)" + +#: core/validators.py:565 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"El atributo \"%(attr)s\" de la línea %(line)s tiene un valor que no es " +"válido. (La línea empieza por \"%(start)s\".)" + +#: template/defaultfilters.py:490 +msgid "yes,no,maybe" +msgstr "si,no,talvez" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "Se creó con éxito %(verbose_name)." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "Se actualizó con éxito %(verbose_name)s." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "Se eliminó %(verbose_name)s." + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Asegúrese de que este valor tenga como máximo %d caracteres." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Asegúrese de que este valor tenga al menos %d caracteres." + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Asegúrese de que este valor sea menor o igual a %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Asegúrese de que este valor sea mayor o igual a %s." + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "Ingrse una fecha válida." + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "Introduzca una hora válida." + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "Introduzca una fecha/hora válida." + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "Introduzca un valor válido." + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "Introduzca una URL válida." + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "La URL parece ser un enlace roto." + +#: newforms/fields.py:360 newforms/models.py:164 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" +"Seleccione una opción válida. Esa opción no es una de las opciones " +"disponibles." + +#: newforms/fields.py:378 newforms/fields.py:454 newforms/models.py:181 +msgid "Enter a list of values." +msgstr "Introduzca una lista de valores." + +#: newforms/fields.py:387 newforms/models.py:187 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "" +"Seleccione una opción válida. %s no es una de las opciones disponibles." + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Asegúrese de que su texto tiene menos de %s caracter." +msgstr[1] "Asegúrese de que su texto tiene menos de %s caracteres." + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "No se permiten saltos de línea." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Seleccione una opción válida; '%(data)s' no está en %(choices)s." + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "El archivo enviado está vacío." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Introduzca un número entero entre -32.768 y 32.767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Introduzca un número positivo." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Introduzca un número entero entre 0 y 32.767." + +#~ msgid "Use '[algo]$[salt]$[hexdigest]'" +#~ msgstr "Use '[algoritmo]$[salt]$[hexdigest]'" + +#~ msgid "Have you forgotten your password?" +#~ msgstr "¿Ha olvidado su contraseña?" + +#~ msgid "%(content_type_name)s" +#~ msgstr "tipos de contenido" + +#~ msgid "%(result_count)s result" +#~ msgid_plural "%(counter)s results" +#~ msgstr[0] "un resultado" +#~ msgstr[1] "%(counter)s resultados" + +#~ msgid "Comment" +#~ msgstr "Comentario" + +#~ msgid "Comments" +#~ msgstr "Comentarios" + +#~ msgid "String (up to 50)" +#~ msgstr "Cadena (máximo 50)" + +#~ msgid "label" +#~ msgstr "etiqueta" + +#~ msgid "package" +#~ msgstr "paquete" + +#~ msgid "packages" +#~ msgstr "paquetes" diff --git a/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..d8e3cc6fd6a488b72c0ce397e8b8c69fe4d35bc7 GIT binary patch literal 1576 zcwTjr!EYQj6vhpk4P^@jN`VSh!U9Et!z7!K&}AuzC7Y;OWtwVtNz|iv<}vGZ#$MT; zrCV{OCxnCqH@I>^9QhBpb43CnAr6QG5*#>i)Gt~bT zxB-3>_x}WLgTFPyc`vVqI09bQCr7HG;>r<$nc}gY0n#a9wz#fEe)2+T*+eU*bS`}h7Z}0DtNIGPx+Xi zpzre$8&OG_;|pCIo=vsP=vDg~7b@pjDYM(0ji%T-s&P;hrk5DkWIazPSHT)jYqK== zjr14^R~r~?tixDoV-V*SSx3u-n?|1^adMC+$7HP3s>}PQLKT`1#9WLrBWI3puD4r5 znrY*b)D|*N-m43njCGf{ZXS)3T|>fQYtk2vx_qnMzM8bRlC2%Sd9}OUN!qWs+i*;V zG?(`O{C0MN-=VNB85^N&DV#Q4J`|NS8izJ`Xv?pes{L+J-C0-U-f5)=>E2o8zRq2( z94UlY%#g#;9k($ng;a0hUWKvb-aZ%|C7sUBo5|*xv(NVdnPg98I+v>G^3FuM)&tQM zh6+e*&GKB@nN||wJCmN&QG9UOE9I%+wGw=aio?TZs1Aa2I@i|6z#MK7Hk>PYwlt(t zpR%K@E}f602Bkaduh7{^PzZ}-*hmdE=2mJWHIQqO+5UDpeiiob, 2006,2007. +# +msgid "" +msgstr "" +"Project-Id-Version: Django JavaScript 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-25 17:48-0300\n" +"PO-Revision-Date: 2007-02-25 17:55-0300\n" +"Last-Translator: Ramiro Morales \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s disponibles" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Seleccionar todos" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Agregar" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Eliminar" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s elegidos" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Seleccione los items a agregar y haga click en " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Eliminar todos" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Enero Febrero Marzo Abril Mayo Junio Julio Agosto Setiembre Octubre " +"Noviembre Diciembre" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Domingo Lunes Martes Miércoles Jueves Viernes Sábado" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M M J V S" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Mostrar" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Ocultar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "Ahora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "Reloj" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "Elija una hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "Medianoche" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "6 a.m." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "Mediodía" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "Cancelar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "Hoy" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "Calendario" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "Ayer" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "Mañana" diff --git a/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..024d2ad98b1e5ede4edd8d27d78283f78c0c5db9 GIT binary patch literal 34867 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 F{x5LfVbuTt literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 0000000..275feaa --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,2036 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-08-05 14:41+0300\n" +"PO-Revision-Date: 2006-08-12 23:41+0300\n" +"Last-Translator: Antti Kaihola \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "Syöttämäsi %s ei kelpaa." + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "Erottele tunnisteet pilkuilla." + +#: db/models/fields/related.py:620 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +" Pidä \"Ctrl\"-näppäin (tai Macin \"Command\") pohjassa valitaksesi useita " +"vaihtoehtoja." + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Syöttämäsi %(self)s-tunniste %(value)r ei kelpaa." +msgstr[1] "Syöttämäsi %(self)s-tunnisteet %(value)r eivät kelpaa." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s, jolla on tämä %(fieldname)s, on jo olemassa." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Tämä kenttä vaaditaan." + +#: db/models/fields/__init__.py:340 +msgid "This value must be an integer." +msgstr "Tarvitaan kokonaisluku." + +#: db/models/fields/__init__.py:372 +msgid "This value must be either True or False." +msgstr "Tarvitaan tosi (True) tai epätosi (False)." + +#: db/models/fields/__init__.py:388 +msgid "This field cannot be null." +msgstr "Tämän kentän arvo ei voi olla \"null\"." + +#: db/models/fields/__init__.py:415 core/validators.py:127 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Päivämäärän pitää olla muodossa VVVV-KK-PP." + +#: db/models/fields/__init__.py:477 core/validators.py:135 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Ajankohdan pitää olla muodossa VVVV-KK-PP TT:MM." + +#: db/models/fields/__init__.py:571 +msgid "Enter a valid filename." +msgstr "Tiedostonimi ei kelpaa." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "arabia" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "bengali" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "tšekki" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "wales" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "tanska" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "saksa" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "kreikka" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "englanti" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "espanja" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Argentiinan espanja" + +#: conf/global_settings.py:49 +msgid "French" +msgstr "ranska" + +#: conf/global_settings.py:50 +msgid "Galician" +msgstr "" + +#: conf/global_settings.py:51 +msgid "Hungarian" +msgstr "unkari" + +#: conf/global_settings.py:52 +msgid "Hebrew" +msgstr "heprea" + +#: conf/global_settings.py:53 +msgid "Icelandic" +msgstr "islanti" + +#: conf/global_settings.py:54 +msgid "Italian" +msgstr "italia" + +#: conf/global_settings.py:55 +msgid "Japanese" +msgstr "japani" + +#: conf/global_settings.py:56 +msgid "Dutch" +msgstr "hollanti" + +#: conf/global_settings.py:57 +msgid "Norwegian" +msgstr "norja" + +#: conf/global_settings.py:58 +msgid "Brazilian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Romanian" +msgstr "romania" + +#: conf/global_settings.py:60 +msgid "Russian" +msgstr "venäjä" + +#: conf/global_settings.py:61 +msgid "Slovak" +msgstr "slovakia" + +#: conf/global_settings.py:62 +msgid "Slovenian" +msgstr "slovenia" + +#: conf/global_settings.py:63 +msgid "Serbian" +msgstr "serbia" + +#: conf/global_settings.py:64 +msgid "Swedish" +msgstr "ruotsi" + +#: conf/global_settings.py:65 +msgid "Tamil" +msgstr "" + +#: conf/global_settings.py:66 +msgid "Ukrainian" +msgstr "ukraina" + +#: conf/global_settings.py:67 +msgid "Simplified Chinese" +msgstr "kiina (yksinkertaistettu)" + +#: conf/global_settings.py:68 +msgid "Traditional Chinese" +msgstr "kiina (perinteinen)" + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Tässä voidaan käyttää vain kirjaimia (a-z), numeroita (0-9) ja alaviivoja (_)." + +#: core/validators.py:67 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Tässä voidaan käyttää vain kirjaimia (a-z), numeroita (0-9) sekä ala-, tavu- ja kauttaviivoja (_ - /)." + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "Isot kirjaimet (ABC) eivät kelpaa tässä." + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "Pienet kirjaimet (abc) eivät kelpaa tässä." + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "Vain pilkulla erotetut luvut kelpaavat tässä." + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Syötä sähköpostiosoitteita pilkuilla erotettuina." + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "IP-osoite ei kelpaa." + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "Tätä kohtaa ei voi jättää tyhjäksi." + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "Vain numerot (0-9) kelpaavat tässä." + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "Tarvitaan vähintään yksi merkki, joka ei ole numero (0-9)." + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "Syötä kokonaisluku." + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "Vain kirjaimet kelpaavat tässä." + +#: core/validators.py:131 +msgid "Enter a valid time in HH:MM format." +msgstr "Ajan täytyy olla muodossa TT:MM." + +#: core/validators.py:139 +msgid "Enter a valid e-mail address." +msgstr "Syötä kelvollinen sähköpostiosoite." + +#: core/validators.py:151 core/validators.py:379 forms/__init__.py:661 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Tiedostoa ei lähetetty. Tarkista lomakkeen koodaus (encoding)." + +#: core/validators.py:155 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Kuva ei kelpaa. Lähettämäsi tiedosto ei ole kuva, tai tiedosto on vioittunut." + +#: core/validators.py:162 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Osoittessa %s ei ole kuvaa tai se on vioittunut." + +#: core/validators.py:166 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Puhelinnumeron tulee olla muodossa XXX-XXX-XXXX. \"%s\" ei kelpaa." + +#: core/validators.py:174 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Osoitteessa %s ei ole QuickTime-videota tai se on vioittunut." + +#: core/validators.py:178 +msgid "A valid URL is required." +msgstr "URL-osoite ei kelpaa." + +#: core/validators.py:192 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"HTML-koodi ei kelpaa. Virheilmoitus on:\n" +"%s" + +#: core/validators.py:199 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Vääränmuotoinen XML: %s" + +#: core/validators.py:209 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL-osoite %s ei kelpaa." + +#: core/validators.py:213 core/validators.py:215 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Osoite %s on rikkoutunut tai väärä linkki." + +#: core/validators.py:221 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Syötä USA:n osavaltion lyhenne." + +#: core/validators.py:236 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Sanaa \"%s\" ei saa käyttää tässä." +msgstr[1] "Sanoja \"%s\" ei saa käyttää tässä." + +#: core/validators.py:243 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Arvon täytyy olla sama kuin kentässä '%s'." + +#: core/validators.py:262 +msgid "Please enter something for at least one field." +msgstr "Täytä ainakin yksi kenttä." + +#: core/validators.py:271 core/validators.py:282 +msgid "Please enter both fields or leave them both empty." +msgstr "Täytä tai jätä tyhjäksi kummatkin kentät." + +#: core/validators.py:289 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Tämä kenttä pitää täyttää, jos %(field)s on %(value)s." + +#: core/validators.py:301 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Tämä kenttä pitää täyttää, jos %(field)s ei ole %(value)s." + +#: core/validators.py:320 +msgid "Duplicate values are not allowed." +msgstr "Samaa arvoa ei voi käyttää kahdesti." + +#: core/validators.py:343 +#, python-format +msgid "This value must be a power of %s." +msgstr "Tämän luvun on oltava %s:n potenssi." + +#: core/validators.py:354 +msgid "Please enter a valid decimal number." +msgstr "Desimaaliluku ei kelpaa." + +#: core/validators.py:356 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Desimaaliluvussa saa tässä olla yhteensä vain %s merkitsevä numero. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." +msgstr[1] "Desimaaliluvussa saa tässä olla yhteensä vain %s merkitsevää numeroa. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." + +#: core/validators.py:359 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Desimaaliluvun kokonaisosassa saa tässä olla vain %s numero. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." +msgstr[1] "Desimaaliluvun kokonaisosassa saa tässä olla vain %s numeroa. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." + +#: core/validators.py:362 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Tässä saa olla vain %s desimaali. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." +msgstr[1] "Tässä saa olla vain %s desimaalia. Huomaa, että desimaalierottimena käytetään pilkun (,) sijasta pistettä (.)." + +#: core/validators.py:372 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Lähetä vähintään %s tavun kokoinen tiedosto." + +#: core/validators.py:373 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Lähetä enintään %s tavun kokoinen tiedosto." + +#: core/validators.py:390 +msgid "The format for this field is wrong." +msgstr "Muoto ei kelpaa." + +#: core/validators.py:405 +msgid "This field is invalid." +msgstr "Tämä arvo ei kelpaa." + +#: core/validators.py:441 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Tietoja ei voida noutaa kohteesta: %s." + +#: core/validators.py:444 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "Osoitteesta %(url)s saatiin virheellinen Content-Type '%(contenttype)s'." + +#: core/validators.py:477 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "Rivillä %(line)s oleva tagi %(tag)s pitää sulkea. (Rivi alkaa \"%(start)s\")" + +#: core/validators.py:481 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "Rivillä %(line)s on tekstiä, joka ei kelpaa tässä yhteydessä. (Rivi alkaa \"%(start)s\")" + +#: core/validators.py:486 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "Rivillä %(line)s attribuutti %(attr)s ei kelpaa. (Rivi alkaa \"%(start)s\")" + +#: core/validators.py:491 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "Rivillä %(line)s tagi \"<%(tag)s>\" ei kelpaa. (Rivi alkaa \"%(start)s\")" + +#: core/validators.py:495 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "Rivillä %(line)s yhdestä tagista puuttuu yksi tai useampi attribuutti. (Rivi alkaa \"%(start)s\")" + +#: core/validators.py:500 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "Rivillä %(line)s attribuutin %(attr)s arvo ei kelpaa. (Rivi alkaa \"%(start)s\")" + +#: contrib/auth/forms.py:52 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Selaimesi ei salli evästeitä. Sisäänkirjautuminen vaatii evästeen." + +#: contrib/auth/forms.py:59 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "Käyttäjätunnus tai salasana ei kelpaa. Huomaa, että isot ja pienet kirjaimet ovat merkitseviä." + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "Tämä käyttäjätili ei ole voimassa." + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nimi" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "koodinimi" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "oikeus" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "oikeudet" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "ryhmä" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "ryhmät" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "tunnus" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr " Vaaditaan. Enintään 30 kirjanta (a-z), numeroa (0-9) tai alaviivaa (_)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "etunimi" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "sukunimi" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "sähköposti" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "salasana" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "(Salasanaa ei näytetä selväkielisenä)" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "ylläpitäjä" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Ylläpitäjillä on pääsy tähän sivuston ylläpito-osioon." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "voimassa" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "Määrää, voiko käyttäjä kirjautua sisään. Tällä voi estää käyttäjätilin käytön poistamatta sitä." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "pääkäyttäjä" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "Antaa käyttäjälle kaikki oikeudet ilman, että niitä täytyy erikseen luetella." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "viimeksi kirjautunut" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "liittynyt" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Tässä valittujen oikeuksien lisäksi käyttäjä saa myös kaikki niiden ryhmien " +"oikeudet, joiden jäsen hän on." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "käyttäjän oikeudet" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "käyttäjä" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "käyttäjät" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Henkilökohtaiset tiedot" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Oikeudet" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Tärkeät päivämäärät" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Ryhmät" + +#: contrib/auth/models.py:256 +msgid "message" +msgstr "viesti" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Kirjautunut ulos" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "tapahtumahetki" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "kohteen tunniste" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "kohteen tiedot" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "tapahtumatyyppi" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "selitys" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "lokimerkintä" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "lokimerkinnät" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                        By %s:

                                        \n" +"
                                          \n" +msgstr "" +"

                                          Yksi %s:

                                          \n" +"
                                            \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Kaikki" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Mikä tahansa päivä" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Tänään" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Viimeiset 7 päivää" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Tässä kuussa" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Tänä vuonna" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Kyllä" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Ei" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Tuntematon" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Kirjaudu sisään" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "Kirjaudu uudelleen sisään, koska istuntosi on mennyt umpeen. Muutoksesi ovat silti tallessa." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "Selaimesi ei salli evästeitä. Muuta asetukset sallimaan evästeet, lataa tämä sivu uudelleen ja yritä toistamiseen." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Käyttäjätunnuksessa ei saa olla '@'-merkkiä." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Käyttäjätunnus ei ole sama kuin sähköpostiosoite. Kokeile '%s'." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Sivuston ylläpito" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:14 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" on nyt lisätty." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:345 +#: contrib/admin/views/auth.py:19 +msgid "You may edit it again below." +msgstr "Voit muokata sitä uudelleen alla." + +#: contrib/admin/views/main.py:269 contrib/admin/views/main.py:354 +#, python-format +msgid "You may add another %s below." +msgstr "Uusi %s on lisättävissä alla." + +#: contrib/admin/views/main.py:287 +#, python-format +msgid "Add %s" +msgstr "Uusi %s" + +#: contrib/admin/views/main.py:333 +#, python-format +msgid "Added %s." +msgstr "Lisätty %s." + +#: contrib/admin/views/main.py:333 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 +msgid "and" +msgstr "ja" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Changed %s." +msgstr "Muokattu: %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Deleted %s." +msgstr "Poistettu %s." + +#: contrib/admin/views/main.py:340 +msgid "No fields changed." +msgstr "Ei muutoksia kenttiin." + +#: contrib/admin/views/main.py:343 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" on nyt muutettu." + +#: contrib/admin/views/main.py:351 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" on nyt lisätty. Voit muokata sitä uudelleen alla." + +#: contrib/admin/views/main.py:389 +#, python-format +msgid "Change %s" +msgstr "Muokkaa: %s" + +#: contrib/admin/views/main.py:471 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Yksi tai useampi %(fieldname)s kohteessa %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:476 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Yksi tai useampi %(fieldname)s kohteessa %(name)s:" + +#: contrib/admin/views/main.py:509 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" on poistettu." + +#: contrib/admin/views/main.py:512 +msgid "Are you sure?" +msgstr "Oletko varma?" + +#: contrib/admin/views/main.py:534 +#, python-format +msgid "Change history: %s" +msgstr "Muokkaushistoria: %s" + +#: contrib/admin/views/main.py:568 +#, python-format +msgid "Select %s" +msgstr "Valitse %s" + +#: contrib/admin/views/main.py:568 +#, python-format +msgid "Select %s to change" +msgstr "Valitse muokattava %s" + +#: contrib/admin/views/main.py:744 +msgid "Database error" +msgstr "Tietokantavirhe" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Kokonaisluku" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Totuusarvo: joko tosi (True) tai epätosi (False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Merkkijono (enintään %(maxlength)s merkkiä)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Pilkulla erotetut kokonaisluvut" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Päivämäärä" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Päivämäärä ja kellonaika" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Sähköpostios." + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Tiedostopolku" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Desimaaliluku" + +#: contrib/admin/views/doc.py:304 contrib/comments/models.py:85 +msgid "IP address" +msgstr "IP-osoite" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Totuusarvo: joko tosi (True), epätosi (False) tai ei mikään (None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relaatio emomalliin" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Puhelinnumero" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Tekstiä" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Kellonaika" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL-osoite" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "USA:n osavaltio (kaksikirjaiminen versaalein)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML-teksti" + +#: contrib/admin/views/auth.py:25 +msgid "Add user" +msgstr "Uusi käyttäjä" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "Kaikki päivät" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Näytä kaikki" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Ohjeita" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Vaihda salasana" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/comments/templates/comments/form.html:6 +msgid "Log out" +msgstr "Kirjaudu ulos" + +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/base.html:29 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Home" +msgstr "Etusivu" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Poista" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "Kohteen '%(escaped_object)s' (%(object_name)s) poisto poistaisi myös siihen liittyviä kohteita, mutta sinulla ei ole oikeutta näiden kohteiden poistamiseen:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "Haluatko varmasti poistaa kohteen \"%(escaped_object)s\" (%(object_name)s)? Myös seuraavat kohteet poistettaisiin samalla:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Kyllä, olen varma" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Sivua ei löydy" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Pahoittelemme, pyydettyä sivua ei voitu löytää." + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Lisää uusi" + +#: contrib/admin/templates/admin/change_form.html:20 +#: contrib/admin/templates/admin/object_history.html:5 +msgid "History" +msgstr "Muokkaushistoria" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Näytä lopputulos" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Korjaa virhe." +msgstr[1] "Korjaa virheet." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Järjestys" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Järjestysnumero:" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " %(filter_title)s:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Tallenna uutena" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Tallenna ja lisää seuraava" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Tallenna välillä ja jatka muokkaamista" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Tallenna ja poistu" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Lisää uusi %(name)s" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Sovelluksen %(name)s mallit." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Muokkaa" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Sinulla ei ole oikeutta muokata mitään." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Viimeisimmät muutokset" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Sinun tekemäsi muutokset" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ei yhtään" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django-sivuston ylläpito" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Djangon ylläpito" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Pvm/klo" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Käyttäjä" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Toiminto" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "Tällä kohteella ei ole muutoshistoriaa. Sitä ei ole lisätty tämän ylläpitosivun avulla." + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Palvelinvirhe" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Palvelinvirhe (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Palvelinvirhe (500)" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "Tietokanta-asennuksessasi on jotain vialla. Varmistu, että sopivat taulut on luotu ja että oikea käyttäjä voi lukea tietokantaa." + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Etsi" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 hakutulas" +msgstr[1] "%(counter)s hakutulosta" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "yhteensä %(full_result_count)s" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Suodatin" + +#: contrib/admin/templates/admin/login.html:17 +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +msgid "Username:" +msgstr "Käyttäjätunnus" + +#: contrib/admin/templates/admin/login.html:20 +#: contrib/comments/templates/comments/form.html:8 +msgid "Password:" +msgstr "Salasana:" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Oletko unohtanut salasanasi?" + +#: contrib/admin/templates/admin/base.html:24 +msgid "Welcome," +msgstr "Tervetuloa," + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "Syötä ensin käyttäjätunnus ja salasana. Sen jälkeen voit muokata muita käyttäjän tietoja." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Käyttäjätunnus" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Salasana:" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Salasana toistamiseen" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Syötä sama salasana tarkistuksen vuoksi toistamiseen." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Kirjanmerkkiset" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Ohjeiden kirjanmerkkiset" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                            To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                            \n" +msgstr "\n

                                            Asenna kirjanmerkkinen raahaamalla linkki kirjanmerkkien työkalupalkkiin tai napsauttamalla linkkiä oikeanpuoleisella hiiren painikkeella ja valitsemalla kirjanmerkkeihin lisäämisen. Sen jälkeen voit valita kirjanmerkkisen miltä tahansa sivuston sivulta. Huomaa, että jotkin näistä kirjanmerkkisistä toimivat vain, jos selaat sivustoa \"paikalliseksi\" määritellyltä tietokoneelta (kysy lisätietoja verkkonne ylläpitäjältä).

                                            \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Tämän sivun ohjeita" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "Vie avoinna olevan sivun luoneen näkymän ohjeisiin." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Näytä kohteen tunniste" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "Näyttää yksittäistä kohdetta vastaavilla sivuilla kohteen tyypin ja tunnisteen." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Muokkaa tätä kohdetta (tässä ikkunassa)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Siirtyy yksittäistä kohdetta vastaavalta sivulta kohteen ylläpitosivulle." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Muokkaa tätä kohdetta (uudessa ikkunassa)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Kuten yllä, mutta avaa ylläpitosivun uuteen ikkunaan." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Pvm:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Klo:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Tällä hetkellä:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Muokkaa:" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Salasanan nollaus" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Unohditko salasanasi? Syötä alle sähköpostiosoitteesi, niin \n" +"lähetämme sinulle uuden salasanan." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Sähköpostiosoite:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Nollaa salasana" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Sait tämän viestin, koska pyysit uutta salasanaa" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "sivuston %(site_name)s käyttäjätilillesi" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Uusi salasanasi on: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Voit vaihtaa salasanan sivulla:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Käyttäjätunnuksesi siltä varalta, että olet unohtanut sen:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Kiitos vierailustasi sivuillemme!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s ylläpitäjät" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Kiitos sivuillamme viettämästäsi ajasta." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Kirjaudu uudelleen sisään" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Salasanan nollaus onnistui" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Uusi salasanasi on lähetetty antamaasi sähköpostiosoitteeseen.\n" +"Se saapuu tuota pikaa." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Salasanan muuttaminen" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Syötä vanha salasanasi varmistukseksi, ja syötä sitten uusi salasanasi\n" +"kaksi kertaa, jotta se tulee varmasti oikein." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Vanha salasana:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Uusi salasana:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Varmista uusi salasana:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Vaihda salasana" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Salasanan muuttaminen onnistui" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Salasanasi on muutettu." + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domain-nimi" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "näyttönimi" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "sivusto" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sivustot" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Esimerkki: '/tietoja/yhteystiedot/'. Varmista, että sekä alussa että lopussa " +"on kauttaviiva." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "otsikko" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "sisältö" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "salli kommentit" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "mallipohjan nimi" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "Esimerkiksi: 'flatpages/yhteydenotto.html'. Jos tämä jätetään tyhjäksi, käytetään oletuspohjaa 'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "vaaditaan rekisteröityminen" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Jos tämä kohta on valittu, vain sisäänkirjautuneet käyttäjät näkevät sivun." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "tekstisivu" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "tekstisivut" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "ohjaa osoitteesta" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Tässä on käytettävä absoluuttista polkua ilman verkkotunnusta. Esimerkki: " +"'\\\n" +"events/search/'" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "ohjaa osoitteeseen" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Tässä on käytettävä joko absoluuttista polkua (kuten yllä) tai täydellistä " +"'http://'-alkuista URL-osoitetta." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "edelleenohjaus" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "edelleenohjaukset" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "kohteen tunniste" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "otsikko" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "kommentti" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "1. pisteytys" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "2. pisteytys" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "3. pisteytys" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "4. pisteytys" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "5. pisteytys" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "6. pisteytys" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "7. pisteytys" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "8. pisteytys" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "on sallittu pisteytys" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "lähetyshetki" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "on julkinen" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "on poistettu" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Rastita jos kommentti on asiaankuulumaton. Kommentin tilalla näytetään\n" +"viesti \"Tämä kommentti on poistettu\"." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "kommentit" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Kommentoitu kohde" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +" Kirjoittanut %(user)s, pvm %(date)s\\n\n" +" \\n\n" +" %(comment)s\\n\n" +" \\n\n" +" http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "henkilön nimi" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP-osoite" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "ylläpidon hyväksymä" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "vapaa kommentti" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "vapaat kommentit" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "pisteet" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "pisteytyspäivä" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "karma-pisteytys" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karma-pisteytykset" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d pistettä käyttäjältä %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +" %(user)s on merkinnyt tämän kommentin:\\n\n" +" \\n\n" +" %(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "merkintäpäivä" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "käyttäjän merkki" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "käyttäjien merkit" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Käyttäjän %r merkki" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "poistamispäivä" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "valvojan poisto" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "valvojien poistot" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Valvojan %r poisto" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonyymit käyttäjät eivät voi äänestää" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Kommentin tunniste on virheellinen" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Itseään ei voi äänestää" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Tämä pisteytys on annettava, koska olet syöttänyt ainakin yhden muunkin pisteytyksen." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Kommentin kirjoittanut käyttäjä on kirjoittanut vain yhden kommentin:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Kommentin kirjoittanut käyttäjä on kirjoittanut alle %(count)s kommenttia:\n" +"\n" +"%(text)s" + +# Mitä "sketchy user" tarkoittaa? +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Tämä on \"sketchy\"-käyttäjän kirjoittama kommentti:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Vain POST-kutsut sallittu" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Yksi tai useampi vaadittu kenttä on jäänyt täyttämättä" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Kommenttilomaketta on käpälöity (turvallisuusrike)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "Kommenttilomakkeen 'target'-parametri ei kelpaa -- kohteen ID oli virheellinen" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Kommenttilomake ei pyytänyt esikatselua tai lähettämistä" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Unohditko salasanasi?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Pisteytykset" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Vaaditaan" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Vapaavalintainen" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Lähetä valokuva" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Kommentti:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Esikatsele kommenttia" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Nimesi:" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "istunnon avain" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "istunnon tiedot" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "vanhenee" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "istunto" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "istunnot" + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "mallin python-luokan nimi" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "sisältötyyppi" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "sisältötyypit" + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Varmista, että tekstin pituus on vähemmän kuin %s merkki." +msgstr[1] "Varmista, että teksti pituus on vähemmän kuin %s merkkiä." + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "Rivinvaihtoja ei voi käyttää." + +#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Valinta ei kelpaa; '%(data)s' ei löydy vaihtoehtojen %(choices)s joukosta." + +#: forms/__init__.py:663 +msgid "The submitted file is empty." +msgstr "Lähetetty tiedosto on tyhjä." + +#: forms/__init__.py:719 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Syötä kokonaisluku väliltä -32768 ja 32767." + +#: forms/__init__.py:729 +msgid "Enter a positive number." +msgstr "Syötä positiivinen kokonaisluku." + +#: forms/__init__.py:739 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Syötä kokonaisluku väliltä 0 ja 32767." + +#: utils/dates.py:6 +msgid "Monday" +msgstr "maanantai" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "tiistai" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "keskiviikko" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "torstai" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "perjantai" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "lauantai" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "sunnuntai" + +#: utils/dates.py:14 +msgid "January" +msgstr "tammikuu" + +#: utils/dates.py:14 +msgid "February" +msgstr "helmikuu" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "maaliskuu" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "huhtikuu" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "toukokuu" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "kesäkuu" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "heinäkuu" + +#: utils/dates.py:15 +msgid "August" +msgstr "elokuu" + +#: utils/dates.py:15 +msgid "September" +msgstr "syyskuu" + +#: utils/dates.py:15 +msgid "October" +msgstr "lokakuu" + +#: utils/dates.py:15 +msgid "November" +msgstr "marraskuu" + +#: utils/dates.py:16 +msgid "December" +msgstr "joulukuu" + +#: utils/dates.py:19 +msgid "jan" +msgstr "tam" + +#: utils/dates.py:19 +msgid "feb" +msgstr "hel" + +#: utils/dates.py:19 +msgid "mar" +msgstr "maa" + +#: utils/dates.py:19 +msgid "apr" +msgstr "huh" + +#: utils/dates.py:19 +msgid "may" +msgstr "tou" + +#: utils/dates.py:19 +msgid "jun" +msgstr "kes" + +#: utils/dates.py:20 +msgid "jul" +msgstr "hei" + +#: utils/dates.py:20 +msgid "aug" +msgstr "elo" + +#: utils/dates.py:20 +msgid "sep" +msgstr "syy" + +#: utils/dates.py:20 +msgid "oct" +msgstr "lok" + +#: utils/dates.py:20 +msgid "nov" +msgstr "mar" + +#: utils/dates.py:20 +msgid "dec" +msgstr "jou" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "tammi" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "helmi" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "elo" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "syys" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "loka" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "marras" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "joulu" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "vuosi" +msgstr[1] "vuotta" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "kuukausi" +msgstr[1] "kuukautta" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "viikko" +msgstr[1] "viikkoa" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "päivä" +msgstr[1] "päivää" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "tunti" +msgstr[1] "tuntia" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuutti" +msgstr[1] "minuuttia" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j.n.Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j.n.Y G:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "G:i" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "N Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "N j, Y" + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "kyllä,ei,ehkä" diff --git a/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/fi/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..34b397e4ba764c455405da8593a8486330449543 GIT binary patch literal 1529 zcwSYK&2J+$7{*PT02{u`hlB*e5<;+AO-TYRC6v`>yXkJ5q>Y+sS6qU5%61Z1^j+-{&OYj{tbK@{Jj$E`~{wX_bM^(ht)``M!lnI)H|t0y#{;*{0zJv z>Q!UiKdVvioodW`4@(EIc3Vub%tWe<+0Skx5igsUV@&`YtRvTc@Fd0 zMQrHlv4#CiSk^hM=Bund8nN~%Nkzv>@Yf2qMM~%qSr|GKifv7dDFi3(WG?Q1sq&x@ zx(rc*Y}`{Oo3dTfp6r}&i(^|FeX`kvw`W!=*mJ(?wZNk0yzR%naC|6cPUOeJ^8L)2 zz&&#sH*ScmG+1Abv>ZpC(&YkV8;% zgPdsbX1%_aG@42MUEXN4)*4spjd~r9$$>bP#RGopNqr-!ukprOtNG5A`djepQQ?x* zk}ecEV_Uqfos+yva$*$mO`1mQ8s*2*87)>blV3%c9(x*#Bo&mmc(1*8^DxNY?DeiG zxoYhAYOTN1?_4z4SgqH#jCMk!-h2i@PJH6toGFs}J={FmLb&%2)9qyA!Y}ChNZ6#K zGc%HU+~ONhQDgOW)|18g+4&jwzL2a>laWzMB1L#$-S8%H*^&)}_1+OXA|)Lj;#4SQ zG|B1zw(vET@za;#>3nh`v_|s9SvN$nl=56oz2~=tibkIhRFqqO;>xYm_^Am-S3qC% zmO5G;Rh|sz^D?GqrWCVSOXbe4@yf$s&9_;j$@b=s4U8, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-08-05 15:27+0300\n" +"Last-Translator: Antti Kaihola \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Mahdolliset %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Valitse kaikki" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Lisää uusi" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Poista" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Valitut %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Valitse vasemmalta ja napsauta " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Tyhjennä kaikki" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Tammikuu Helmikuu Maaliskuu Huhtikuu Toukokuu Kesäkuu Heinäkuu Elokuu " +"Syyskuu Lokakuu Marraskuu Joulukuu" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Sunnuntai Maanantai Tiistai Keskiviikko Torstai Perjantai Lauantai" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "S M T K T P L" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Nyt" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Kello" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Valitse kellonaika" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "24" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "06" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "12" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Peruuta" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Tänään" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalenteri" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Eilen" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Huomenna" diff --git a/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..14b8a935a1acefb7805096d8647061b564a15853 GIT binary patch literal 39707 zcwW7I37j2OmG^CTBcS5GQba?ip}Uh1Afb~0NjfXp(^(k^)$dhzSMpxfOD)~$I1pAr zRz;GKj5;Esi3)Cv`?$3`Zm2j6GH&Cz%(#7STOkgg>2tU(|Ac=wSQLJ>8goz|PZI?pDC(1AY(?=$Nko zJ_+zaz{g9!fF}svn*KAurvm;)^Z#4FA9n`vJmC!DIUVpBfU^Lf2)O7B)?W^I8Q@yL z69K;j_$447$oB{Y`z}`;cvkFj1w+-;+fO|TL$LqD+Tebc> zHT`{nCj)+5;nx8%1?GoZ{&#>+lkos1fK$3imwjCv&mDlz1iTaQB*6D}vENSuP67N; z7svUlF1GvoF1GiSZjSHS-8!Cb(q)FGU(ikb`np;Fl5UQ_+Rc9bZuS>x{VTiK?)8B8 z0^Sa|4sh`dW6qUtz(K$-&)|HYGn06)nn`+J0{B9}+Dy*Zb%3V=-Zhi`e|jeSyMHFf z@v!FqJK#$IPnc!QRKU|`k#4I2F;uf@7WwUJK>TOkg+D;ed>?Qt;4{xO=6QfS0WSc& z>P*si4Dd9-N6zHDJRVE{7B|nGP5PWan|!fxHp^c(n|Qo&Hrx9s;L8BNspbDToBVRp zS;hcm^D;oNsOdY4^w@V6``dpO+x^H{#P^qg7XnT_+n73Fa5m@Rk+V4u-vgWr`0r

                                            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 oF3dQkQ0^%meJveu3Hf2u&fk^lez literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 0000000..62f3b46 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,2513 @@ +# translation of django.po to french +# This file is distributed under the same license as the PACKAGE package. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER. +# Laurent Rahuel , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-03-11 11:46+0100\n" +"PO-Revision-Date: 2006-05-08 15:12+0200\n" +"Last-Translator: Baptiste Goupil \n" +"Language-Team: français \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: oldforms/__init__.py:357 db/models/fields/__init__.py:116 +#: db/models/fields/__init__.py:273 db/models/fields/__init__.py:609 +#: db/models/fields/__init__.py:620 newforms/models.py:177 +#: newforms/fields.py:78 newforms/fields.py:374 newforms/fields.py:450 +#: newforms/fields.py:461 +msgid "This field is required." +msgstr "Ce champ est obligatoire." + +#: oldforms/__init__.py:392 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Assurez-vous que votre texte fait moins de %s caractère." +msgstr[1] "Assurez-vous que votre texte fait moins de %s caractères." + +#: oldforms/__init__.py:397 +msgid "Line breaks are not allowed here." +msgstr "Les retours à la ligne ne sont pas autorisés ici." + +#: oldforms/__init__.py:498 oldforms/__init__.py:571 oldforms/__init__.py:610 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Sélectionnez un choix valide ; '%(data)s' n'est pas dans %(choices)s." + +#: oldforms/__init__.py:577 newforms/widgets.py:170 +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Inconnu" + +#: oldforms/__init__.py:577 newforms/widgets.py:170 +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Oui" + +#: oldforms/__init__.py:577 newforms/widgets.py:170 +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Non" + +#: oldforms/__init__.py:672 core/validators.py:174 core/validators.py:445 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: oldforms/__init__.py:674 +msgid "The submitted file is empty." +msgstr "Le fichier soumis est vide." + +#: oldforms/__init__.py:730 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Entrez un nombre entier entre -32 768 et 32 767." + +#: oldforms/__init__.py:740 +msgid "Enter a positive number." +msgstr "Entrez un nombre entier positif." + +#: oldforms/__init__.py:750 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Entrez un nombre entier entre 0 et 32 767." + +#: db/models/manipulators.py:307 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/manipulators.py:308 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "et" + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s avec le champ %(fieldname)s existe déjà." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Cette valeur doit être un entier." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Cette valeur doit être soit Vraie soit Fausse." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Ce champ ne peut pas être vide." + +#: db/models/fields/__init__.py:456 core/validators.py:148 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Entrez une date valide au format AAAA-MM-JJ." + +#: db/models/fields/__init__.py:525 core/validators.py:157 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Entrez une date et une heure valide au format AAAA-MM-JJ HH:MM." + +#: db/models/fields/__init__.py:629 +msgid "Enter a valid filename." +msgstr "Entrez un nom de fichier valide." + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Entrez un %s valide." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Séparez les ID par des virgules." + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Maintenez \"Contrôle (ctrl)\", ou \"Commande (touche pomme)\" sur un Mac, " +"pour en sélectionner plusieurs." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Entrez un ID %(self)s valide. La valeur %(value)r est invalide." +msgstr[1] "" +"Entrez des ID %(self)s valides. Les valeurs %(value)r sont invalides." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Arabe" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Indien" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "Catalan" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "Tchèque" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "Gallois" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "Dannois" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "Allemand" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "Grec" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "Anglais" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "Espagnol" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "Espagnol Argentin" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "Dannois" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "Français" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "Galicien" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "Hongrois" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "Israélien" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "Islandais" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "Italien" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "Japonais" + +#: conf/global_settings.py:58 +msgid "Kannada" +msgstr "Kannada" + +#: conf/global_settings.py:59 +msgid "Latvian" +msgstr "Letton" + +#: conf/global_settings.py:60 +msgid "Macedonian" +msgstr "Macédonien" + +#: conf/global_settings.py:61 +msgid "Dutch" +msgstr "Néerlandais" + +#: conf/global_settings.py:62 +msgid "Norwegian" +msgstr "Norvégien" + +#: conf/global_settings.py:63 +msgid "Polish" +msgstr "Polonais" + +#: conf/global_settings.py:64 +msgid "Brazilian" +msgstr "Brésilien" + +#: conf/global_settings.py:65 +msgid "Romanian" +msgstr "Roumain" + +#: conf/global_settings.py:66 +msgid "Russian" +msgstr "Russe" + +#: conf/global_settings.py:67 +msgid "Slovak" +msgstr "Slovaque" + +#: conf/global_settings.py:68 +msgid "Slovenian" +msgstr "Slovaque" + +#: conf/global_settings.py:69 +msgid "Serbian" +msgstr "Serbe" + +#: conf/global_settings.py:70 +msgid "Swedish" +msgstr "Suédois" + +#: conf/global_settings.py:71 +msgid "Tamil" +msgstr "Tamoul" + +#: conf/global_settings.py:72 +msgid "Telugu" +msgstr "Télougou" + +#: conf/global_settings.py:73 +msgid "Turkish" +msgstr "Turc" + +#: conf/global_settings.py:74 +msgid "Ukrainian" +msgstr "Ukrainien" + +#: conf/global_settings.py:75 +msgid "Simplified Chinese" +msgstr "Chinois simplifié" + +#: conf/global_settings.py:76 +msgid "Traditional Chinese" +msgstr "Chinois traditionnel" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "" +"Ce champ ne doit contenir que des lettres, des nombres et des tirets bas " +"('_')." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Ce champ ne doit contenir que des lettres, des nombres, des tirets bas ('_') " +"et des '/'." + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"Ce champ ne doit contenir que des lettres, des nombres, des tirets bas ('_') " +"et des '-'." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Les lettres majuscules ne sont pas autorisées ici." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Les lettres minuscules ne sont pas autorisées ici." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Saisissez uniquement des chiffres séparés par des virgules." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Entrez des adresses de courriel valides séparées par des virgules." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Entrez une adresse IP valide." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "Vous ne pouvez pas laisser ce champ vide." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Les caractères non numériques ne sont pas autorisés ici." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Cette valeur ne peut pas être composé uniquement de chiffres." + +#: core/validators.py:120 newforms/fields.py:126 +msgid "Enter a whole number." +msgstr "Entrez un nombre entier." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Seules les lettres de l'alphabet sont autorisées ici." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "L'année doit être supérieure à 1900." + +#: core/validators.py:143 +#, fuzzy, python-format +msgid "Invalid date: %s" +msgstr "URL invalide : %s" + +#: core/validators.py:153 +msgid "Enter a valid time in HH:MM format." +msgstr "Entrez une heure valide au format HH:MM." + +#: core/validators.py:162 newforms/fields.py:269 +msgid "Enter a valid e-mail address." +msgstr "Entrez une adresse de courriel valide." + +#: core/validators.py:178 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envoyez une image valide. Le fichier que vous avez transferé n'est pas une " +"image ou bien est une image corrompue." + +#: core/validators.py:185 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "L'URL %s ne pointe pas vers une image valide." + +#: core/validators.py:189 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Les numéros de téléphone doivent être au format XXX-XXX-XXXX. \"%s\" est " +"incorrect." + +#: core/validators.py:197 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "L'URL %s ne pointe pas vers une vidéo QuickTime valide." + +#: core/validators.py:201 +msgid "A valid URL is required." +msgstr "Une URL valide est requise." + +#: core/validators.py:215 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Du HTML valide est requis. Les erreurs sont les suivantes :\n" +"%s" + +#: core/validators.py:222 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formé : %s" + +#: core/validators.py:239 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL invalide : %s" + +#: core/validators.py:244 core/validators.py:246 +#, python-format +msgid "The URL %s is a broken link." +msgstr "L'URL %s est un lien cassé." + +#: core/validators.py:252 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Entrez une abréviation d'état américain valide." + +#: core/validators.py:266 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Attention à votre langage ! Le mot %s n'est pas autorisé ici." +msgstr[1] "Attention à votre langage ! Les mots %s ne sont pas autorisés ici." + +#: core/validators.py:273 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Ce champ doit correspondre au champ '%s'." + +#: core/validators.py:292 +msgid "Please enter something for at least one field." +msgstr "Saisissez au moins une valeur dans un des champs s'il vous plaît." + +#: core/validators.py:301 core/validators.py:312 +msgid "Please enter both fields or leave them both empty." +msgstr "" +"Renseignez chacun des champs ou laissez les deux vides s'il vous plaît." + +#: core/validators.py:320 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Ce champ doit être renseigné si %(field)s vaut %(value)s" + +#: core/validators.py:333 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Ce champ doit être renseigné si %(field)s ne vaut pas %(value)s" + +#: core/validators.py:352 +msgid "Duplicate values are not allowed." +msgstr "Des valeurs identiques ne sont pas autorisées." + +#: core/validators.py:367 +#, fuzzy, python-format +msgid "This value must be between %(lower)s and %(upper)s." +msgstr "Cette valeur doit être entre %(lower)s et %(upper)s." + +#: core/validators.py:369 +#, fuzzy, python-format +msgid "This value must be at least %s." +msgstr "Cette valeur doit être au moins %s." + +#: core/validators.py:371 +#, fuzzy, python-format +msgid "This value must be no more than %s." +msgstr "Cette valeur ne doit pas dépasser %s." + +#: core/validators.py:407 +#, python-format +msgid "This value must be a power of %s." +msgstr "Cette valeur doit être une puissance de %s." + +#: core/validators.py:418 +msgid "Please enter a valid decimal number." +msgstr "Saisissez un nombre décimal valide s'il vous plaît." + +#: core/validators.py:422 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Saisissez un nombre décimal valide avec au plus %s chiffre s'il vous plaît." +msgstr[1] "" +"Saisissez un nombre décimal valide avec au plus %s chiffres s'il vous plaît." + +#: core/validators.py:425 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Veuillez saisir un nombre décimal valide avec au plus %s chiffre." +msgstr[1] "Veuillez saisir un nombre décimal valide avec au plus %s chiffres." + +#: core/validators.py:428 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Veuillez saisir un nombre décimal valide avec au plus %s décimale." +msgstr[1] "Veuillez saisir un nombre décimal valide avec au plus %s décimales." + +#: core/validators.py:438 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Vérifiez que le fichier transféré fait au moins une taille de %s octets." + +#: core/validators.py:439 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Vérifiez que le fichier transféré fait au plus une taille de %s octets." + +#: core/validators.py:456 +msgid "The format for this field is wrong." +msgstr "Le format de ce champ est mauvais." + +#: core/validators.py:471 +msgid "This field is invalid." +msgstr "Ce champ est invalide." + +#: core/validators.py:507 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Impossible de récupérer quoi que ce soit depuis %s." + +#: core/validators.py:510 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"L'entête Content-Type '%(contenttype)s', renvoyée par l'url %(url)s n'est " +"pas valide." + +#: core/validators.py:543 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Veuillez fermer le tag %(tag)s de la ligne %(line)s. (La ligne débutant par " +"\"%(start)s\".)" + +#: core/validators.py:547 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Du texte commençant à la ligne %(line)s n'est pas autorisé dans ce contexte. " +"(Ligne débutant par \"%(start)s\".)" + +#: core/validators.py:552 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" ligne %(line)s n'est pas un attribut valide. (Ligne débutant " +"par \"%(start)s\".)" + +#: core/validators.py:557 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" ligne %(line)s n'est pas un tag valide. (Ligne débutant par \"%" +"(start)s\".)" + +#: core/validators.py:561 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Un tag, ou un ou plusieurs attributs, de la ligne %(line)s est manquant. " +"(Ligne débutant par \"%(start)s\".)" + +#: core/validators.py:566 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"La valeur de l'attribut \"%(attr)s\" de la ligne %(line)s n'est pas valide. " +"(Ligne débutant par \"%(start)s\".)" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "L'objet %(verbose_name)s a été créé avec succès." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "L'objet %(verbose_name)s a été mis à jour avec succès." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "L'objet %(verbose_name)s a été supprimé." + +#: newforms/models.py:164 newforms/fields.py:360 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" +"Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux " +"disponibles." + +#: newforms/models.py:181 newforms/fields.py:378 newforms/fields.py:454 +msgid "Enter a list of values." +msgstr "Entrez une liste de valeur." + +#: newforms/models.py:187 newforms/fields.py:387 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Sélectionnez un choix valide ; %s n'en fait pas partie." + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Assurez-vous que cette valeur fait moins de %d caractère." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Assurez-vous que cette valeur fait au moins %d caractère." + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Assurez-vous que cette valeur soit plus petite ou égale à %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Assurez-vous que cette valeur soit plus grande ou égale à %s." + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "Entrez une date valide." + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "Entrez une heure valide." + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "Entrez une date et une heure valides." + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "Entrez une valeur valide." + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "Entrez une URL valide." + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "L'URL est un lien cassé." + +#: contrib/humanize/templatetags/humanize.py:16 +msgid "th" +msgstr "e" + +#: contrib/humanize/templatetags/humanize.py:16 +msgid "st" +msgstr "er" + +#: contrib/humanize/templatetags/humanize.py:16 +msgid "nd" +msgstr "d" + +#: contrib/humanize/templatetags/humanize.py:16 +msgid "rd" +msgstr "e" + +#: contrib/humanize/templatetags/humanize.py:48 +#, python-format +msgid "%(value).1f million" +msgid_plural "%(value).1f million" +msgstr[0] "%(value).1f million" +msgstr[1] "%(value).1f millions" + +#: contrib/humanize/templatetags/humanize.py:51 +#, python-format +msgid "%(value).1f billion" +msgid_plural "%(value).1f billion" +msgstr[0] "%(value).1f milliard" +msgstr[1] "%(value).1f milliards" + +#: contrib/humanize/templatetags/humanize.py:54 +#, python-format +msgid "%(value).1f trillion" +msgid_plural "%(value).1f trillion" +msgstr[0] "%(value).1f billion" +msgstr[1] "%(value).1f billions" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "one" +msgstr "un" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "two" +msgstr "deux" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "three" +msgstr "trois" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "four" +msgstr "quatre" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "five" +msgstr "cinq" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "six" +msgstr "six" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "seven" +msgstr "sept" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "eight" +msgstr "huit" + +#: contrib/humanize/templatetags/humanize.py:69 +msgid "nine" +msgstr "neuf" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirigé depuis" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Ceci doit être un chemin absolu, sans nom de domaine. Par exemple: '/events/" +"search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirigé vers" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Ceci peut être soit un chemin absolu (voir ci-dessus) soit une URL complète " +"débutant par 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redirige" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redirige" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID de l'objet" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "titre" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "commentaire" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "vote n°1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "vote n°2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "vote n°3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "vote n°4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "vote n°5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "vote n°6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "vote n°7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "vote n°8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "est un vote valide" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "date et heure soumises" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "est public" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "adresse IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "est supprimé" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Cochez cette case si le commentaire est inadéquat. Un message type \"Ce " +"commentaire a été supprimé\" sera affiché en lieu et place de celui-ci." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "commentaires" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Type de contenu" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Posté par %(user)s à %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nom" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "adresse IP" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "approuvé par l'équipe" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "commentaire libre" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "commentaires libres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "evaluation" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "date d'évaluation" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "point de Karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "points de Karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d évalué par %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Ce commentaire a été marqué par %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "date d'indicateur" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "indicateur utilisateur" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "indicateurs utilisateur" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Indicateur par %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "date de suppression" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "suppression de modérateur" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "suppressions de modérateur" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Suppression de modérateur par %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Les utilisateurs anonymes ne peuvent pas voter" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de commentaire invalide" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Impossible de voter pour soi-même" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"Ce votre est nécéssaire parceque vous avez saisi au moins un autre vote." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Ce commentaire a été posté par un utilisateur qui a posté moins de %(count)s " +"commentaire :\n" +"\n" +"%(text)s" +msgstr[1] "" +"Ce commentaire a été posté par un utilisateur qui a posté moins de %(count)s " +"commentaires :\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Ce commentaire a été posté par un utilisateur imprécis :\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Seuls les POSTs sont autorisés" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Un ou plusieurs champs requis n'ont pas été remplis" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" +"Quelqu'un a trafiqué le formulaire de commentaire (violation des règles de " +"sécurité)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"Ce formulaire de commentaire avait un paramètre cible invalide ; l'ID de " +"l'objet était invalide" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" +"Le formulaire de commentaire ne proposait ni les options de prévisualisation " +"ni d'envoi" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Votre nom :" + +#: contrib/comments/templates/comments/freeform.html:5 +#: contrib/comments/templates/comments/form.html:28 +msgid "Comment:" +msgstr "Commentaire :" + +#: contrib/comments/templates/comments/freeform.html:10 +#: contrib/comments/templates/comments/form.html:35 +msgid "Preview comment" +msgstr "Prévisualisation du commentaire" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Nom d'utilisateur" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Déconnexion" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Mot de passe" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Mot de passe oublié?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Votes" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requis" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Optionel" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Poster une photo" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nom de domaine" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nom à afficher" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "site" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sites" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                            By %s:

                                            \n" +"
                                              \n" +msgstr "" +"

                                              Par %s :

                                              \n" +"
                                                \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Tout" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Toutes les dates" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Aujourd'hui" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Les 7 derniers jours" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Ce mois-ci" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Cette année" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "heure de l'action" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id de l'objet" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "représentation de l'objet" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "indicateur de l'action" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "message de modification" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrée d'historique" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entrées d'historique" + +#: contrib/admin/templatetags/admin_list.py:247 +msgid "All dates" +msgstr "Toutes les dates" + +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Saisissez s'il vous plaît un nom d'utilisateur et un mot de passe valide. " +"Remarquez que chacun de ces champs est sensible à la casse (différenciation " +"des majuscules/minuscules)." + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Connectez-vous" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Votre session a expiré, connectez-vous de nouveau s'il vous plaît. Ne vous " +"inquiétez pas, votre travail précédement éffectué a été sauvé." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Il semblerait que votre navigateur n'accepte pas les cookies. Activez-les, " +"rechargez cette page et rééssayez s'il vous plaît." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Les noms d'utilisateur ne peuvent contenir le caractère '@'" + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"Votre courriel n'est pas votre nom d'utilisateur. Essayez '%s' à la place." + +#: contrib/admin/views/auth.py:19 contrib/admin/views/main.py:257 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "L'objet %(name)s \"%(obj)s\" a été ajouté avec succès." + +#: contrib/admin/views/auth.py:24 contrib/admin/views/main.py:261 +#: contrib/admin/views/main.py:347 +msgid "You may edit it again below." +msgstr "Vous pouvez continuez de l'éditez ci-dessous." + +#: contrib/admin/views/auth.py:30 +#, fuzzy +msgid "Add user" +msgstr "Ajouter %s" + +#: contrib/admin/views/auth.py:57 +#, fuzzy +msgid "Password changed successfully." +msgstr "Mot de passe modifié avec succés" + +#: contrib/admin/views/auth.py:64 +#, fuzzy, python-format +msgid "Change password: %s" +msgstr "Modifier votre mot de passe" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Gestion du site" + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Vous pouvez ajouter un autre %s ci-dessous." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Ajouter %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Ajouté %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Modifié %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Supprimé %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Aucun champ modifié." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "L'objet %(name)s \"%(obj)s\" a été modifié avec succès." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"L'objet %(name)s \"%(obj)s\" a été ajouté avec succès.Vous pouvez continuez " +"de l'éditez ci-dessous." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Changement %s" + +#: contrib/admin/views/main.py:476 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Un ou plusieurs %(fieldname)s dans %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:481 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Un ou plusieurs %(fieldname)s dans %(name)s:" + +#: contrib/admin/views/main.py:514 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "L'objet %(name)s \"%(obj)s\" a été supprimé avec succès." + +#: contrib/admin/views/main.py:517 +msgid "Are you sure?" +msgstr "Êtes-vous sûr ?" + +#: contrib/admin/views/main.py:539 +#, python-format +msgid "Change history: %s" +msgstr "Historique des changements : %s" + +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s" +msgstr "Sélectionnez %s" + +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s to change" +msgstr "Sélectionnez %s pour changer" + +#: contrib/admin/views/main.py:768 +msgid "Database error" +msgstr "" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "mot-clé :" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filtre :" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "vue :" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "L'application %r n'a pas été trouvée." + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %(name)r not found in app %(label)r" +msgstr "Le modèle %(name)r n'a pas été trouvé dans l'application %(label)r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%(label)s.%(type)s` object" +msgstr "l'objet `%(label)s.%(type)s en relation " + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "modèle :" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%(label)s.%(name)s` objects" +msgstr "les objets `%(label)s.%(type)s en relation" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "nombre de %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Entier" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Booléen (Vrai ou Faux)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Chaîne de caractère (jusqu'à %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Des entiers séparés par une virgule" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Date (sans l'heure)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Date (avec l'heure)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Courriel :" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Chemin vers le fichier" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Nombre décimal" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Booléen (Vrai, Faux ou None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relation au modèle parent" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Numéro de téléphone" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Texte" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Heure" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "État U.S. (deux lettres majuscules)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "Texte XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s ne semble pas être un objet urlpattern" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Actuellement :" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modification :" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Date :" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Heure :" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Documentation" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Modifier votre mot de passe" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Accueil" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Historique" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Date/Heure" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Utilisateur" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Action" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. N Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Cet objet n'a pas d'historique de modification. Il n'a probablement pas été " +"ajouté au moyen de ce site d'administration." + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Ajouter %(name)s" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Par %(filter_title)s " + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Erreur du serveur" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Erreur du serveur (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Erreur du serveur (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Une erreur est survenue. Elle a été transmise par courriel aux " +"administrateurs du site et sera corrigée dans les meilleurs délais. Merci " +"pour votre patience." + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Envoyer" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 résultat" +msgstr[1] "%(counter)s résultats" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s résultats" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Tout montrer" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Site d'administration de Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administration de Django" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtre" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Cette page n'a pas été trouvée" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Nous sommes désolés, mais la page demandée est introuvable." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modèles disponibles dans l'application %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Ajouter" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modifier" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Vous n'avez pas la permission d'éditer quoi que ce soit." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Actions récentes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mes actions" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Aucun(e) disponible" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Voir sur le site" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Veuillez corriger l'erreur ci-dessous." +msgstr[1] "Veuillez corriger les erreurs ci-dessous." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Tri" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Ordre :" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Bienvenue," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Supprimer" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Supprimer l'objet %(object_name)s '%(escaped_object)s' provoquerait la " +"suppression des objets qui lui sont liés mais votre compte ne possède pas la " +"permission de supprimer les types d'objets suivants :" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Êtes vous certain de vouloir supprimer l'objet %(object_name)s \"%" +"(escaped_object)s\" ? Les éléments suivant sont liés à celui-ci et seront " +"aussi supprimés :" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Oui, j'en suis certain" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Sauver en tant que nouveau" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Sauver et ajouter un nouveau" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Sauver et continuer les modifications" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Sauver" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" +"Entrez un nouveau mot de passe pour l'utilisateur %(username)s." + +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Mot de passe" + +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Mot de passe (à nouveau)" + +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Entrez le même mot de passe que précedemment, par sécurité." + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Entrez tout d'abord un nom d'utilisateur et un mot de passe.Vous pourrez " +"ensuite modifier plus d'options." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Modification de votre mot de passe" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Mot de passe modifié avec succés" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Votre mot de passe a été modifié." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Réinitialisation de votre mot de passe" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Mot de passe perdu ? Saisissez votre adresse de courriel ci-dessous et nous " +"annulerons votre mot de passe actuel avant de vous en faire parvenir un " +"nouveau par courriel." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Courriel :" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Réinitialiser mon mot de passe" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Merci pour le temps que vous avez accordé à ce site aujourd'hui." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Connectez vous à nouveau" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Mot de passe réinitialisé avec succès" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Nous vous avons envoyé par courriel un nouveau mot de passe. Vous devriez le " +"recevoir rapidement." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Pour des raisons de sécurité, veuillez entrer votre ancien mot de passe puis " +"saisissez deux fois votre nouveau mot de passe afin que nous puissions " +"vérifier que vous l'avez tapé correctement." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Ancien mot de passe :" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nouveau mot de passe :" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirmation du nouveau mot de passe" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Modifier mon mot de passe" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Vous recevez ce courriel car vous avez demandé un changement de mot de passe" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "pour votre compte au site %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Votre nouveau mot de passe est : %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Vous pouvez modifier ce mot de passe à l'adresse suivante :" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Votre nom d'utilisateur, en cas d'oubli :" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Merci d'utiliser notre site !" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "L'équipe %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Signets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Documentation des signets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentation pour cette page" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Vous envoie de n'importe quelle page vers la documentation de la vue qui a " +"généré cette page." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Afficher l'ID de l'objet" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Montre le content-type et l'ID unique pour les pages qui représente un objet " +"unique." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editer cet objet (fenêtre courante)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Renvoie à la page d'administration qui représente un objet seul." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editer cet objet (nouvelle fenêtre)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" +"Comme ci-dessus, mais ouvre la page d'administration dans une nouvelle " +"fenêtre." + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "nom du module python" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "type de contenu" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "types de contenu" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Déconnecté" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nom" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nom de code" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permission" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permissions" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "groupe" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "groupes" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "nom d'utilisateur" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"Requis. 30 caractères maximum, alphanumériques uniquement (lettres, " +"chiffres, et tirets bas '_')." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "prénom" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "nom" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "courriel" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "mot de passe" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"Utilisez [algo]$[salt]$[hexdigest]' ou le formulaire " +"de changement de mot de passe." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "statut équipe" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Précise si l'utilisateur peut se connecter à ce site d'administration." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "actif" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Précise si l'utilisateur peut se connecter à l'administration. " +"Déselectionnezceci plutôt que supprimer le compte." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "statut super-utilisateur" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"Précise que l'utilisateur possède toutes les permissions sans les assigner " +"explicitement." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "dernière connexion" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "date d'inscription" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"En plus des permissions qui lui sont manuellement assignées, cet utilisateur " +"recevra aussi toutes les permissions de tous les groupes auquels il " +"appartient. " + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "permissions de l'utilisateur" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "utilisateur" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "utilisateurs" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Information personnelle" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Permissions" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Dates importantes" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Groupes" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "message" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "Les deux mots de passe ne correspondent pas." + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "Un utilisateur avec ce nom existe déjà." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Votre navigateur ne semble pas avoir activé les cookies. Les cookies sont " +"nécessaire pour se connecter" + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "Ce compte est inactif." + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" +"Cette adresse e-mail ne correspond à aucun compte utilisateur. Êtes-vous sûr " +"de vous être enregistré ?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "Les deux nouveaux mots de passe ne correspondent pas." + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "Votre ancien mot de passe est incorrect. Veuillez le rectifier." + +#: contrib/localflavor/uk/forms.py:18 +msgid "Enter a postcode. A space is required between the two postcode parts." +msgstr "" + +#: contrib/localflavor/usa/forms.py:17 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "clé de session" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "donnée de session" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "date d'expiration" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "session" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sessions" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Par exemple : '/about/contact/'. Vérifiez la présence du caractère '/' en " +"début et en fin de chaine." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titre" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contenu" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "autoriser les commentaires" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nom du template" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Par exemple: 'flatfiles/contact_page'. Sans définition, le système utilisera " +"'flatfiles/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "enregistrement requis" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Si coché, seuls les utilisateurs connectés auront la possibilité de voir " +"cette page." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "page à plat" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "pages à plat" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Lundi" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Mardi" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Mercredi" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Jeudi" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Vendredi" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Samedi" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Dimanche" + +#: utils/dates.py:14 +msgid "January" +msgstr "Janvier" + +#: utils/dates.py:14 +msgid "February" +msgstr "Février" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Mars" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Avril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Mai" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Juin" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Juillet" + +#: utils/dates.py:15 +msgid "August" +msgstr "Août" + +#: utils/dates.py:15 +msgid "September" +msgstr "Septembre" + +#: utils/dates.py:15 +msgid "October" +msgstr "Octobre" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembre" + +#: utils/dates.py:16 +msgid "December" +msgstr "Décembre" + +#: utils/dates.py:19 +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "fév" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "avr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jui" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "aout" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sep" + +#: utils/dates.py:20 +msgid "oct" +msgstr "oct" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "déc" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Fév." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Aôut" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Oct." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Déc." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "année" +msgstr[1] "années" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mois" +msgstr[1] "mois" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semaine" +msgstr[1] "semaines" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "journée" +msgstr[1] "jours" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "heure" +msgstr[1] "heures" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minute" +msgstr[1] "minutes" + +#: utils/dateformat.py:40 +msgid "p.m." +msgstr "après-midi" + +#: utils/dateformat.py:41 +msgid "a.m." +msgstr "matin" + +#: utils/dateformat.py:46 +msgid "PM" +msgstr "Matin" + +#: utils/dateformat.py:47 +msgid "AM" +msgstr "Après-midi" + +#: utils/dateformat.py:95 +msgid "midnight" +msgstr "minuit" + +#: utils/dateformat.py:97 +msgid "noon" +msgstr "midi" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j F Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j F Y, G:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "G:i:s" + +#: utils/translation/trans_real.py:380 +#, fuzzy +msgid "YEAR_MONTH_FORMAT" +msgstr "j F Y" + +#: utils/translation/trans_real.py:381 +#, fuzzy +msgid "MONTH_DAY_FORMAT" +msgstr "j F Y" + +#: template/defaultfilters.py:491 +msgid "yes,no,maybe" +msgstr "oui,non,peut-être" + +#~ msgid "%dth" +#~ msgstr "%de" + +#~ msgid "Have you forgotten your password?" +#~ msgstr "" +#~ "Avez vous perdu votre mot de passe?" + +#~ msgid "Use '[algo]$[salt]$[hexdigest]'" +#~ msgstr "Utilisez '[algo]$[salt]$[hexdigest]'" + +#~ msgid "Comment" +#~ msgstr "Commentaire" + +#~ msgid "Comments" +#~ msgstr "Commentaires" + +#~ msgid "String (up to 50)" +#~ msgstr "Chaîne de caractères (jusqu'à 50)" + +#~ msgid "label" +#~ msgstr "intitulé" + +#~ msgid "package" +#~ msgstr "paquetage" + +#~ msgid "packages" +#~ msgstr "paquetages" + +#~ msgid "Messages" +#~ msgstr "Messages" diff --git a/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/fr/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..12a53b39e278353831a51b6da497123cebf4f741 GIT binary patch literal 1533 zcwU8*y>A>v7{&+t4CKP6fk;Rojf7B$&>m-pSk7T0Hoh~l@!6K|Vhf38Zuj-w#IrNU z?934(D*gs2D50eQBr53WkSM8|_s+-rG4DP1+hXH$hVK%_ zOBlakyo~Y5Bdq%V0v`qMgY)1S_yqV5_&9iBE}k!fPl7LkP4F^!0bH)R1}=ab;B9bw zF8Y53Ho&jvV*GFGc;DCYegrxAGq?)=S;u*0KKfsouk(P1sA+9J#`_G@W8fE!80X7I ztm~V4{|E3{@TW$6&##Rb=l6PjufBH%4#AK#)OC9y#{Xa;#uE$io?3``9M}Rs1+RkN zffvC)YXAGS{}~us9PAg^`FAO%dgyx^;~5tF6KY&!F;{qh$c>g%HHY|5VLXrVTEa<^XRR<;M%0-c| zaZj1-m~9E|g+1XLG_sY^7dD&lbZVu7eZsfACQRytr+(~9#|JcZRE)^--OQQ59W#p? zUCJth^<}Q*c;eWOF&fiHY@Z6;VgugiLw<;{!3S(WiZaJfjJG_S7@5&)twc{HCyIqTy== z)}&gadaX@46$KaWKkLX6wT|o!MABm~#*_?66diPfG=9XjBm0h$En~+wn*Ggw@BGC} zt#)(WXh#|aoJ>)nqr2{nsS;AZg`Yy$lHA*ygWY6x_1g7h>HKo2_K0lK)0xSo9(VZa zNV+Cl>9pG{y<4lgSIB*E#ar~uF?Ru>*L^CZIf0s zgV!x%l^Ije>x5UbQ-Qi>c;&EF3sYbi<=jFN@XJB1HdEzDOQh|!tt, 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-24 16:39+0100\n" +"PO-Revision-Date: 2005-12-24 16:39+0100\n" +"Last-Translator: Mikaël Barbero \n" +"Language-Team: French \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/calendar.js:24 +#: contrib/admin/media/js/dateparse.js:26 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Janvier Février Mars Avril Mai Juin Juillet Août Septembre Octobre Novembre " +"Décembre" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M M J V S" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Dimanche Lundi Mardi Mercredi Jeudi Vendredi Samedi" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Disponible %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Tout choisir" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Ajouter" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Enlever" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Choisi %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Sélectionnez un ou plusieurs choix et cliquez " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Tout enlever" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Maintenant" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Horloge" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Choisir une heure" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Minuit" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6:00" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Midi" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Annuler" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Aujourd'hui" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendrier" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Hier" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Demain" diff --git a/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..00beabebf8feeac136f7864a2b39b1fd37bc7e48 GIT binary patch literal 32192 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

                                                X#MTwow&#QF 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 N%RM00qU}cJ{{m~uzSjT% literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.po new file mode 100644 index 0000000..394f9cd --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/django.po @@ -0,0 +1,1988 @@ +# Translation of django.po to Galego +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Afonso Fernández Nogueira , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:11+0200\n" +"PO-Revision-Date: 2006-07-03 14:06+0200\n" +"Last-Translator: Afonso Fernández Nogueira \n" +"Language-Team: Galego\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Galician\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID do obxecto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "título" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentario" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "é unha puntuación válida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/hora do envío" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "é público" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "Enderezo IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "está borrado" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Marque esta caixa se o comentario non é apropiado. Verase a mensaxe \"Este " +"comentario foi borrado\" no canto do seu contido." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "comentarios" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Obxecto de contido" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Publicado por %(user)s o %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nome da persoa" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "enderezo IP" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprobado polos moderadores" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "comentario libre" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "comentarios libres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "puntuación" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data da puntuación" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "puntos de karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "puntos de karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentario foi marcado por %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "data da marca" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "marca de usuario" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "marcas de usuario" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Marca por %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data de borrado" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "borrado de moderador" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "borrados de moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Borrado polo moderador %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Os usuarios anónimos non poden votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de comentario non válida" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Vostede non se pode votar a si mesmo" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Este comentario foi publicado por un usuario que ten publicados menos de %" +"(count)s comentario:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Este comentario foi publicado por un usuario que ten publicados menos de %" +"(count)s comentarios:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Soamente se permiten envíos polo método POST" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Non se enviaron un ou máis dos campos requiridos" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Alguén manipulou o formulario do comentario (violación de seguridade)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"O formulario do comentario tiña un parámetro 'target' non válido: a ID do " +"obxecto non é válida" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "O formulario de comentario non proporciona 'preview' ou 'post'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Usuario:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Contrasinal:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Esqueceu o contrasinal?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Rematar sesión" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requirido" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Publicar unha foto" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Comentario:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Previsualizar comentario" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Nome:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                By %s:

                                                \n" +"
                                                  \n" +msgstr "" +"

                                                  Por %s:

                                                  \n" +"
                                                    \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Todo" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Calquera data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Hoxe" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últimos 7 días" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Este mes" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Este ano" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Si" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Non" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Descoñecido" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "hora da acción" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id do obxecto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr do obxecto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "código do tipo de acción" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "cambiar mensaxe" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada de rexistro" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entradas de rexistro" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Todas as datas" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "Insira un nome de usuario e un contrasinal correctos. Teña en conta que " +"nos dous campos se distingue entre maiúsculas e minúsculas." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Iniciar sesión" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Ten que identicarse outra vez porque a súa sesión expirou. Non se preocupe, " +"o que enviou quedou gardado." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Semella que o seu navegador non está configurado para aceptar 'cookies'. " +"Por favor, habilite as 'cookies', recargue a páxina e ténteo de novo." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Os nomes de usuario non poden conter o carácter '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"O seu enderezo de correo electrónico non é o seu nome de usuario. Probe con " +"'%s'." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Administración do sitio web" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "Engadiuse correctamente o/a %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Pode editalo embaixo." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Pode engadir outro/a %s embaixo." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Engadir %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Engadido/a %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "e" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Modificado(s) %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Eliminado(s) %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Non se modificou ningún campo." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "Modificouse correctamente o/a %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "Engadiuse correctamente o/a %(name)s \"%(obj)s\" Pode editalo embaixo." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Un ou máis %(fieldname)s no/a %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Un ou máis %(fieldname)s no/a %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "Eliminouse correctamente o/a %(name)s \"%(obj)s\"." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Está seguro?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Histórico de cambios: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Seleccione un/ha %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Seleccione %s que modificar" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Número enteiro" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Valor booleano (verdadeiro ou falso)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Cadea (ata %(maxlength)s caracteres)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Números enteiros separados por comas" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Data (sen a hora)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Data (coa hora)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Enderezo de correo electrónico" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Ruta do ficheiro" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Número decimal" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Booleano (verdadeiro, falso ou ningún)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Relación cun modelo pai" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Número de teléfono" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Texto" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Estado dos Estados Unidos (dúas letras maíusculas)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "Texto XML" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Documentación" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Cambiar contrasinal" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Inicio" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Histórico" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Usuario" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Acción" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j de N de Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Este obxecto non ten histórico de cambios. Posibelmente non se creou usando " +"este sitio de administración." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Administración de sitio Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administración de Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Erro do servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Erro do servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Erro do servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Houbo un erro. Xa se informou aos administradores do sitio por correo " +"electrónico e debería quedar arranxado pronto. Grazas pola súa paciencia." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Páxina non atopada" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Sentímolo, pero non se atopou a páxina solicitada." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelos dispoñíbeis na aplicación %(name)s." + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Engadir" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Non ten permiso para editar nada." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Accións recentes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "As miñas accións" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ningunha dispoñíbel" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Engadir %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Esqueceu o contrasinal?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Benvido," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Eliminar" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Borrar o %(object_name)s '%(object)s' resultaría na eliminación de elementos " +"relacionados, pero a súa conta non ten permiso para borrar os seguintes " +"tipos de elementos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Seguro que quere borrar o %(object_name)s \"%(object)s\"? Eliminaranse os " +"seguintes obxectos relacionados:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Si, estou seguro" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr " Por %(title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Ir" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Ver na web" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Por favor, corrixa o erro de embaixo." +msgstr[1] "Por favor, corrixa os erros de embaixo." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Orde" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Orde:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Gardar coma novo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Gardar e engadir outro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Gardar e seguir editando" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Gardar" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Cambiar o contrasinal" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "O seu contrasinal cambiouse correctamente." + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Cambiouse o seu contrasinal." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Recuperar o contrasinal" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Esqueceu o contrasinal? Introduza o seu enderezo de correo electrónico " +"embaixo e enviarémoslle un novo contrasinal." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Enderezo de correo electrónico:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Recuperar o meu contrasinal" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Grazas polo tempo que dedicou ao sitio web." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Entrar de novo" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "O contrasinal foi recuperado correctamente" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Acabamos de enviarlle un novo contrasinal ao enderezo de correo indicado. " +"Debería recibilo en breve." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Por razóns de seguridade, introduza o contrasinal actual. Despois introduza " +"dúas veces o contrasinal para verificarmos que o escribiu correctamente." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Contrasinal actual:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Contrasinal novo:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirmar contrasinal:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Cambiar o contrasinal" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Recibe esta mensaxe porque solicitou recuperar o contrasinal" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "para a súa conta de usuario en %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "O seu novo contrasinal é: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Pode cambiar este contrasinal visitando esta páxina:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "No caso de que o esquecese, o seu nome de usuario é:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Grazas por usar o noso sitio web!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "O equipo de %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Bookmarklets de documentación" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                    To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                    \n" +msgstr "" +"\n" +"

                                                    Para instalar bookmarklets, arrastre a ligazón á súa\n" +"barra de favoritos ou marcadores, ou faga clic co botón dereito\n" +"e engádao aos marcadores. Agora pode usar o bookmarklet dende\n" +" calquera páxina do sitio web. Teña en conta que algúns destes\n" +"bookmarklets precisan que estea a visitar o sitio dende un ordenador\n" +"designado coma \"interno\" (fale co administrador do sistema se\n" +"non está seguro de que o seu ordenador é \"interno\" .

                                                    \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentación para esta páxina" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "Salta á documentación para a vista que xera a páxina." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Amosar ID do obxecto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Amosa o tipo de contido e a ID única para páxinas que representan un obxecto " +"determinado." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar este obxecto (nesta fiestra)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Salta á páxina de administración para páxina que representan un obxecto " +"determinado." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar este obxecto (nunha nova fiestra)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Como enriba, pero abre a páxina de administración nunha nova fiestra." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Agora:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "orixe da redirección" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Debe ser unha ruta absoluta, sen o nome de dominio. Exemplo: '/events/" +"search/'" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "destino da redirección" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Pode ser unha ruta absoluta (coma a de enriba) ou un URL completo que empece " +"por 'http://'" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "redirección" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "redireccións" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Exemplo: '/about/contact/'. Lembre incluír as barras ao principio e ao final." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "título" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contido" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "activar comentarios" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nome da plantilla" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Exemplo: 'flatpages/contact_page'. Se non se especifica, o sistema usará " +"'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "require rexistro" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Se se marca, só poderán ver a páxina os usuarios identificados." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "páxina simple" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "páxinas simples" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "nome" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "código" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "permiso" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "permisos" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "grupo" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "grupos" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "nome de usuario" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "nome" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "apelidos" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "enderezo de correo electrónico" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "contrasinal" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Use '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "membro do persoal" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Indica se o usuario pode entrar neste sitio de administración." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "activo" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "estado de superusuario" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "última sesión" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "data de rexistro" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Ademais dos permisos asignados manualmente, este usuario gozará de todos os " +"permisos concedidos a cada un dos grupos aos que pertence." + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "permisos de usuario" + +#: contrib/auth/models.py:70 +msgid "user" +msgstr "usuario" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "usuarios" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Información persoal" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Permisos" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Datas importantes" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grupos" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "mensaxe" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Semella que o seu navegador non acepta 'cookies'. Requírense " +"'cookies' para iniciar sesión." + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "nome do módulo Python" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "tipo de contido" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "tipos de contido" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "clave da sesión" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "datos da sesión" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "data de caducidade" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "sesión" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sesións" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "dominio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nome" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "sitio" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sitios" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "d-m-Y" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "d-m-Y H:i" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "luns" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "martes" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "mércores" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "xoves" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "venres" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "sábado" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "domingo" + +#: utils/dates.py:14 +msgid "January" +msgstr "xaneiro" + +#: utils/dates.py:14 +msgid "February" +msgstr "febreiro" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "marzo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "maio" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "xuño" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "xullo" + +#: utils/dates.py:15 +msgid "August" +msgstr "agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "setembro" + +#: utils/dates.py:15 +msgid "October" +msgstr "outubro" + +#: utils/dates.py:15 +msgid "November" +msgstr "novembro" + +#: utils/dates.py:16 +msgid "December" +msgstr "decembro" + +#: utils/dates.py:19 +msgid "jan" +msgstr "xan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "xuñ" + +#: utils/dates.py:20 +msgid "jul" +msgstr "xul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "out" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dec" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "xan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "out." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "dec." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "ano" +msgstr[1] "anos" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mes" +msgstr[1] "meses" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semana" +msgstr[1] "semanas" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "día" +msgstr[1] "días" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "horas" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minutos" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "bengalí" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "checo" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "galés" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "dinamarqués" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "alemán" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "grego" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "inglés" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "español" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "francés" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "galego" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "húngaro" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "hebreo" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "islandés" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "italiano" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "xaponés" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "holandés" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "noruegués" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "brasileiro" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "romanés" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "ruso" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "eslovaco" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "esloveno" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "serbio" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "sueco" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "ucraíno" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "chinés simplificado" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "chinés tradicional" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Este valor soamente pode conter letras, números e guións baixos (_)." + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Este valor soamente pode conter letras, números, guións baixos (_), guións (-) e barras " +"inclinadas (/)." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Non se permiten letras maiúsculas." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Non se permiten letras minúsculas." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Insira só díxitos separados por comas." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Insira enderezos de correo elecrónico válidos separados por comas." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Insira un enderezo IP válido." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Non se permiten valores en branco." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Non se permiten caracteres non númericos." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Este valor non pode estar composto por díxitos soamente." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Insira un número enteiro." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Soamente se permiten caracteres do alfabeto." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Insira unha data válida en formato AAAA-MM-DD." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Insira unha hora válida en formato HH:MM." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Insira unha data/hora válida en formato AAAA-MM-DD HH:MM." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Insira un enderezo de correo electrónico válido." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Suba unha imaxe válida. O ficheiro subido non era unha imaxe ou esta estaba " +"corrupta." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "O URL %s non apunta a unha imaxe válida." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Os números de teléfono deben estar no formato XXX-XXX-XXXX. \"%s\" non é " +"válido." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "O URL %s non apunta a unha vídeo QuickTime válido." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Precísase un URL válido." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Precísase HTML válido. Os erros específicos son estes:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formado: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL non válido: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "O URL %s é unha ligazón rota." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Insira unha abreviatura estatal válida para un dos Estados Unidos." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Sen palabrotas, por favor! Non se pode usar a palabra %s aquí." +msgstr[1] "Sen palabrotas, por favor! Non se poden usar as palabras %s aquí." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Este campo ten que coincidir co campo '%s'." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Por favor, encha polo menos un campo." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Por favor, encha os dous campos ou deixe ambos en branco." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Débese encher este campo se %(field)s é %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Este campo débese encher se %(field)s non é %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Non se permiten valores duplicados." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Este valor ten que ser unha potencia de %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Insira un número decimal válido." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Insira un número decimal válido cun máximo de %s díxito en total." +msgstr[1] "Insira un número decimal válido cun máximo de %s díxitos en total." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Insira un número decimal válido cun máximo de %s lugar decimal." +msgstr[1] "Insira un número decimal válido cun máximo de %s lugares decimais." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Verifique que o ficheiro subido ten un tamaño mínimo de %s bytes." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Verifique que o ficheiro subido ten un tamaño máximo de %s bytes." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "O formato deste campo é incorrecto." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Este campo non é válido." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Non se puido recibir ningún dato de %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"O URL %(url)s devolveu a cabeceira Content-Type non válida '%(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Por favor, peche a etiqueta %(tag)s da liña %(line)s. (A liña comeza con \"%" +"(start)s\")." + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Algún texto a partir da liña %(line)s non é válido nese contexto. (A liña " +"comeza con \"%(start)s\")." + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" na liña %(line)s non é un atributo válido. (A liña comeza con " +"\"%(start)s\")." + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" na liña %(line)s non é unha etiqueta válida. (A liña comeza " +"con \"%(start)s\")." + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Falta un o máis dos atributos requiridos para unha etiqueta da liña %(line)" +"s. (A liña comeza con \"%(start)s\")." + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"O atributo \"%(attr)s\" na liña %(line)s contén un valor non válido. (A liña " +"comeza con \"%(start)s\")." + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "Xa existe un obxecto %(object)s con este %(type)s para o campo %(field)s." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "Xa existe un/ha %(optname)s con este/a %(fieldname)s." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Requírese este campo." + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "Este valor ten que ser un número enteiro." + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "Este valor ten que verdadeiro ou falso." + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "Este campo non pode ser nulo." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Introduza un nome de ficheiro válido." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Insira un %s válido/a." + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "Separe IDs múltiplas con comas." + +#: db/models/fields/related.py:581 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +" Para seleccionar máis dunha entrada, manteña premida a tecla \"Control\", " +"ou \"Comando\" nun Mac." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Insira IDs de %(self)s válidas. O valor %(value)r non é válido." +msgstr[1] "" +"Insira IDs de %(self)s válidas. Os valores %(value)r non son válidos." + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Asegúrese de que o seu texto contén menos de %s carácter." +msgstr[1] "Asegúrese de que o seu texto contén menos de %s caracteres." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Aquí non se permiten saltos de liña." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Elixa unha opción válida; '%(data)s' non está en %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "O ficheiro enviado está baleiro." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Insira un número enteiro entre -32.768 e 32.767." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Insira un número positivo." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Insira un número enteiro entre 0 e 32.767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "si,non,quizais" + +#~ msgid "Comment" +#~ msgstr "Comentario" + +#~ msgid "Comments" +#~ msgstr "Comentarios" + +#~ msgid "String (up to 50)" +#~ msgstr "Cadea (ata 50 caracteres)" + +#~ msgid "label" +#~ msgstr "etiqueta" + +#~ msgid "package" +#~ msgstr "paquete" + +#~ msgid "packages" +#~ msgstr "paquetes" diff --git a/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/gl/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..140f9a220eb98b9f1927737f5947e14a707d0831 GIT binary patch literal 1519 zcwSwS&x;&I6vxZm88s7su0KFfcpyPT=*i5;>SkQUad$@(cCvA2lGS5PcWqB4J@tmF z>T!0-$)k5qp4NjP5;Wk=lgvpF{0{=+Eg&f5;K766?rL4Mpz8Cg>ec&I@7`MNGlB6u z=8Kp=VZMZU`yo+|-@%8$KfqP+Pw+ADFYrnCK2lWt#tOvcc9_$#f3El=f zMPBug-@Quj*J^(sEJ|F+&xzAf{6k&*cRVRV4n=(m-)kcHD)K0DL`lj&DeOOi`7Gu$ z)!QQGvz1)rCtfGnoHj)-NknfhbtcD|qH|7c$xN9`WNxh;jh)dhyq=N5t(aagBpExdoEoLA+=HOn{Q zGrF8aHh!^@_j_m5p{x%!xK>mo~=QH_(KM zq^VrqU7m;?lgdO}an4bq-HdT~XRP#TM{2Hm63w~eV$L#2xtU0su$9#b8pd=m&5%EoLVEf9{QxiZDZEl, 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2005-07-02 13:25+0200\n" +"Last-Translator: Afonso Fernández Nogueira \n" +"Language-Team: Galego\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s dispoñíbeis" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Escoller todo" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Engadir" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Quitar" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s escollido/a(s)" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Seleccione unha ou varias entrada e faga clic " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Quitar todo" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"xaneiro febreiro marzo abril maio xuño xullo agosto setembro outubro novembro " +"decembro" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "domingo luns martes mércores xoves venres sábado" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M M X V S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Agora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Reloxo" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Escolla unha hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Medianoite" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 da mañá" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Mediodía" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Cancelar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Hoxe" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendario" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Onte" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Mañá" diff --git a/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..0f8b07e5bbc3e97d13050b7ebff01878da6a549e GIT binary patch literal 36826 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.po new file mode 100644 index 0000000..6e4df17 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/django.po @@ -0,0 +1,1999 @@ +# translation of Django. +# Copyright (C) 2006 THE Django'S COPYRIGHT HOLDER +# This file is distributed under the same license as the Django package. +# +# +msgid "" +msgstr "" +"Project-Id-Version: Django 0.95\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-06-15 12:42+0300\n" +"PO-Revision-Date: 2006-06-15 12:40+0300\n" +"Last-Translator: Meir Kriheli \n" +"Language-Team: Hebrew\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: template/defaultfilters.py:389 +msgid "yes,no,maybe" +msgstr "כן,לא,אולי" + +#: forms/__init__.py:346 db/models/fields/__init__.py:114 +#: db/models/fields/__init__.py:265 db/models/fields/__init__.py:545 +#: db/models/fields/__init__.py:556 +msgid "This field is required." +msgstr "יש להזין תוכן בשדה זה." + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "נא לוודא שהטקסט שלך מכיל פחות מ %s תו." +msgstr[1] "נא לוודא שהטקסט שלך מכיל פחות מ %s תווים." + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "מעברי שורה אסורים כאן." + +#: forms/__init__.py:485 forms/__init__.py:558 forms/__init__.py:597 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "יש לבחור אפשרות חוקית; '%(data)s' אינו בין %(choices)s." + +#: forms/__init__.py:659 core/validators.py:151 core/validators.py:376 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "לא נשלח שום קובץ. נא לבדוק את סוג הקידוד של הטופס." + +#: forms/__init__.py:661 +msgid "The submitted file is empty." +msgstr "הקובץ שנשלח ריק." + +#: forms/__init__.py:717 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "חש להזין מספר שלם בין ‎-32,768 ל- 32,767." + +#: forms/__init__.py:727 +msgid "Enter a positive number." +msgstr "יש להזין מספר חיובי." + +#: forms/__init__.py:737 +msgid "Enter a whole number between 0 and 32,767." +msgstr "יש להזין מספר שלם בין 0 ל- 32,767." + +#: utils/translation.py:363 +msgid "DATE_FORMAT" +msgstr "d.m.Y" + +#: utils/translation.py:364 +msgid "DATETIME_FORMAT" +msgstr "d.m.y H:i:s" + +#: utils/translation.py:365 +msgid "TIME_FORMAT" +msgstr "H:i:s" + +#: utils/translation.py:381 +msgid "YEAR_MONTH_FORMAT" +msgstr "d.m.Y" + +#: utils/translation.py:382 +msgid "MONTH_DAY_FORMAT" +msgstr "d.m.Y" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "שנה" +msgstr[1] "שנים" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "חודש" +msgstr[1] "חודשים" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "שבוע" +msgstr[1] "שבועות" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "יום" +msgstr[1] "ימים" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "שעה" +msgstr[1] "שעות" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "דקה" +msgstr[1] "דקות" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "שני" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "שלישי" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "רביעי" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "חמישי" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "שישי" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "שבת" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "ראשון" + +#: utils/dates.py:14 +msgid "January" +msgstr "ינואר" + +#: utils/dates.py:14 +msgid "February" +msgstr "פברואר" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "מרץ" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "אפריל" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "מאי" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "יוני" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "יולי" + +#: utils/dates.py:15 +msgid "August" +msgstr "אוגוסט" + +#: utils/dates.py:15 +msgid "September" +msgstr "ספטמבר" + +#: utils/dates.py:15 +msgid "October" +msgstr "אוקטובר" + +#: utils/dates.py:15 +msgid "November" +msgstr "נובמבר" + +#: utils/dates.py:16 +msgid "December" +msgstr "תצבר" + +#: utils/dates.py:19 +msgid "jan" +msgstr "יאנ" + +#: utils/dates.py:19 +msgid "feb" +msgstr "פבר" + +#: utils/dates.py:19 +msgid "mar" +msgstr "מרץ" + +#: utils/dates.py:19 +msgid "apr" +msgstr "אפר" + +#: utils/dates.py:19 +msgid "may" +msgstr "מאי" + +#: utils/dates.py:19 +msgid "jun" +msgstr "יונ" + +#: utils/dates.py:20 +msgid "jul" +msgstr "יול" + +#: utils/dates.py:20 +msgid "aug" +msgstr "אוג" + +#: utils/dates.py:20 +msgid "sep" +msgstr "ספט" + +#: utils/dates.py:20 +msgid "oct" +msgstr "אוק" + +#: utils/dates.py:20 +msgid "nov" +msgstr "נוב" + +#: utils/dates.py:20 +msgid "dec" +msgstr "דצמ" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "יאנ'" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "פבר'" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "אוג'" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "ספט'" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "אוק'" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "נוב'" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "דצמ'" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s עם %(type)s קיים כבר עבור %(field)s נתון." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s·עם·%(fieldname)s·זה קיימת כבר." + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "ערך זה חייב להיות מספר שלם." + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "ערך זה חייב להיות אמת או שקר." + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "שדה זה אינו יכול להכיל null." + +#: db/models/fields/__init__.py:471 core/validators.py:135 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "יש להזין תאריך ושעה במבנה YYYY-MM-DD HH:MM." + +#: db/models/fields/__init__.py:565 +msgid "Enter a valid filename." +msgstr "יש להזין שם קובץ חוקי." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "יש להזין %s חוקי." + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "יש להפריד מזהים מרובים בפסיקים." + +#: db/models/fields/related.py:581 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "החזק את \"Control\", או \"Command\" על מק, לחוץ כדי לבחור יותר מאחד." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "נא להזין זיהוי %(self)s חוקי. הערך %(value)r אינו חוקי." +msgstr[1] "" +"נא להזין זיהויי %(self)s חוקיים. הערכים %(value)r אינם חוקיים." + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "ערך זה חייב להכיל אותיות, ספרות וקווים תחתונים בלבד." + +#: core/validators.py:67 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "ערך זה חייב להכיל אותיות, ספרות, מקפים, קווים תחתונים ונטויים בלבד." + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "אסור להשתמש באותיות גדולות." + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "אסור להשתמש באותיות קטנות." + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "יש להזין רק ספרות מופרדות בפסיקים." + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "יש להזין רק כתובות דוא\"ל מופרדות בפסיקים." + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "נא להזין כתובת IP חוקית." + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "חובה להזין ערך בשדה זה." + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "מותר להזין ספרות בלבד." + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "ערך זה אינו יכול להכיל ספרות בלבד." + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "נא להזין מספר שלם." + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "יש להזין כאן אותיות בלבד." + +#: core/validators.py:127 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "יש להזין תאריך במבנה YYYY-MM-DD." + +#: core/validators.py:131 +msgid "Enter a valid time in HH:MM format." +msgstr "יש להזין שעה במבנה HH:MM." + +#: core/validators.py:139 +msgid "Enter a valid e-mail address." +msgstr "יש להזין כתובת דוא\"ל חוקית." + +#: core/validators.py:155 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "נא להעלות תמונה חוקית. הקובץ שהעלת אינו תמונה אומכיל תמונה מקולקלת." + +#: core/validators.py:162 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "ה-URL %s אנו מצביע לתמונה חוקית." + +#: core/validators.py:166 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "מספרי טלפון חייבים להיות במבנה XXX-XXX-XXXX.‏ \"%s\" אינו חוקי." + +#: core/validators.py:174 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "ה-URL‏ %s אינו מצביע לסרטון QuickTime חוקי." + +#: core/validators.py:178 +msgid "A valid URL is required." +msgstr "יש להזין URL חוקי." + +#: core/validators.py:192 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"יש להזין HTML חוקי. שגיאות ספציפיות:\n" +"%s" + +#: core/validators.py:199 +#, python-format +msgid "Badly formed XML: %s" +msgstr "מבנה XML שגוי: %s" + +#: core/validators.py:209 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL שגוי: %s" + +#: core/validators.py:213 core/validators.py:215 +#, python-format +msgid "The URL %s is a broken link." +msgstr "ה-URL‏ %s הוא קישור שבור." + +#: core/validators.py:221 +msgid "Enter a valid U.S. state abbreviation." +msgstr "יש להזין קיצור חוקי למדינה בארה\"ב." + +#: core/validators.py:236 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "שמור על לשונך! המילה %s אסורה לשימוש כאן." +msgstr[1] "שמור על לשונך! המילים %s אסורות לשימוש כאן." + +#: core/validators.py:243 +#, python-format +msgid "This field must match the '%s' field." +msgstr "תוכן השדה חייב להיות זהה לשדה '%s'." + +#: core/validators.py:262 +msgid "Please enter something for at least one field." +msgstr "יש להזין תוכן בלפחות אחד מהשדות." + +#: core/validators.py:271 core/validators.py:282 +msgid "Please enter both fields or leave them both empty." +msgstr "יש להזין תוכן בשני השדות או להשאיר את שניהם ריקים." + +#: core/validators.py:289 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "יש להזין מידע בשדה זה אם שדה %(field)s מכיל %(value)s" + +#: core/validators.py:301 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "יש להזין תוכן בשדה זה אם תוכן שדה %(field)s אינו %(value)s" + +#: core/validators.py:320 +msgid "Duplicate values are not allowed." +msgstr "לא ניתן להזין ערכים כפולים." + +#: core/validators.py:343 +#, python-format +msgid "This value must be a power of %s." +msgstr "ערך זה חייב להיות חזקה של %s." + +#: core/validators.py:354 +msgid "Please enter a valid decimal number." +msgstr "יש להזין מספר עשרוני חוקי." + +#: core/validators.py:356 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "נא להזין מספר עשרוני חוקי עם %s ספרה לכל היותר." +msgstr[1] "" +"נא להזין מספר עשרוני חוקי עם %s ספרות לכל היותר." + +#: core/validators.py:359 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "נא להזין מספר עשרוני חוקי עם %s ספרה אחרי הנקודה לכל היותר." +msgstr[1] "" +"נא להזין מספר עשרוני חוקי עם %s ספרות אחרי הנקודה לכל היותר." + +#: core/validators.py:369 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "יש להעלות קובץ בגודל %s בתים לפחות." + +#: core/validators.py:370 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "יש לוודא שהקובץ שהעלת הוא בגודל %s בתים לכל היותר." + +#: core/validators.py:387 +msgid "The format for this field is wrong." +msgstr "מבנה תוכן שדה זה שגוי." + +#: core/validators.py:402 +msgid "This field is invalid." +msgstr "שדה זה אינו חוקי." + +#: core/validators.py:438 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "לא ניתן לאחזר כלום מ %s." + +#: core/validators.py:441 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "ה-URL·%(url)s·החזיר כותרת·Content-Type·לא חוקית·'%(contenttype)s'." + +#: core/validators.py:474 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "נא לסגור את תג·%(tag)s·בשורה·%(line)s.·(השורה מתחילה ב·\"%(start)s\".)" + +#: core/validators.py:478 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"חלק מהטקסט בשורה·%(line)s·אסור בהקשר זה.·(השורה·מתחילה ב·\"%(start)s\".)" + +#: core/validators.py:483 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\"·בשורה·%(line)s·אינה תכונה חוקית.·(השורה מתחילה ב·\"%(start)s\".)" + +#: core/validators.py:488 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\"·בשורה·%(line)s·אינו תג חוקי.·(השורה מתחילה ב·\"%(start)s\".)" + +#: core/validators.py:492 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"לתג בשורה %(line)s חסרה תכונה אחת או יותר נדרשות. (השורה מתחילה ב-\"%" +"(start)s\".)" + +#: core/validators.py:497 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"לתכונה·\"%(attr)s\"·בשורה·%(line)s·יש ערך לא חוקי.·(השורה·מתחילה ב·\"%(start)" +"s\".)" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "בנגאלית - Bengali" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "צ'כית - Czech" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "וולשית - Welsh" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "דנית - Danish" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "גרמנית - German" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "יוונית - Greek" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "אנגלית - English" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "ספרדית - Spanish" + +#: conf/global_settings.py:45 +msgid "Argentinean Spanish" +msgstr "ספרדית ארגנטינאית - Argentinean Spanish" + +#: conf/global_settings.py:46 +msgid "French" +msgstr "צרפתית - French" + +#: conf/global_settings.py:47 +msgid "Galician" +msgstr "גאליצית - Galician" + +#: conf/global_settings.py:48 +msgid "Hungarian" +msgstr "הונגרית (Hungarian)" + +#: conf/global_settings.py:49 +msgid "Hebrew" +msgstr "עברית - Hebrew" + +#: conf/global_settings.py:50 +msgid "Icelandic" +msgstr "איסלנדית - Icelandic" + +#: conf/global_settings.py:51 +msgid "Italian" +msgstr "איטלקית - Italian" + +#: conf/global_settings.py:52 +msgid "Japanese" +msgstr "יפנית - Japanese" + +#: conf/global_settings.py:53 +msgid "Dutch" +msgstr "הולנדית - Dutch" + +#: conf/global_settings.py:54 +msgid "Norwegian" +msgstr "נורווגית - Norwegian" + +#: conf/global_settings.py:55 +msgid "Brazilian" +msgstr "ברזילאית - Brazilian" + +#: conf/global_settings.py:56 +msgid "Romanian" +msgstr "רומנית - Romanian" + +#: conf/global_settings.py:57 +msgid "Russian" +msgstr "רוסית - Russian" + +#: conf/global_settings.py:58 +msgid "Slovak" +msgstr "סלובקית - Slovak" + +#: conf/global_settings.py:59 +msgid "Slovenian" +msgstr "סלובנית - Slovenian" + +#: conf/global_settings.py:60 +msgid "Serbian" +msgstr "סרבית - Serbian" + +#: conf/global_settings.py:61 +msgid "Swedish" +msgstr "שוודית - Swedish" + +#: conf/global_settings.py:62 +msgid "Ukrainian" +msgstr "אוקראינית - Ukrainian" + +#: conf/global_settings.py:63 +msgid "Simplified Chinese" +msgstr "סינית פשוטה - Simplified·Chinese" + +#: conf/global_settings.py:64 +msgid "Traditional Chinese" +msgstr "סינית מסורתית - Traditional·Chinese" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "מפתח התחברות (session key)" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "מידע התחברות (session data)" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "תאריך פג תוקף" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "התחברות" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "התחברויות" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "שם מתחם" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "שם לתצוגה" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "אתר" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "אתרים" + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "שם ה-class של מודל פייתון" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "סוג תוכן" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "סוגי תוכן" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "מזהה אובייקט" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "כותרת" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "תגובה" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "דירוג #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "דירוג #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "דירוג #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "דירוג #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "דירוג #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "דירוג #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "דירוג #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "דירוג #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "האם דירוג חוקי" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "תאריך/שעת הגשה" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "ציבורי" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:292 +msgid "IP address" +msgstr "כתובת IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "האם הוסר" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"יש לסמן תיבה זו עבור תגובה לא נאותה. הודעת \"תגובה זו נמחקה\" תוצג במקום." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "תגובות" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "אובייקט תוכן" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"הוגש ע\"י %(user)s ב %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "שם" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "כתובת IP" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "אושר ע\"י הצוות" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "הערה אנונימית" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "הערות אנונימיות" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "ציון" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "תאריך ציון" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "ניקוד קארמה" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "ניקודי קארמה" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d·דירוג ע\"י·%(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"התגובה סומנה ע\"י %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "תאריך סימון" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "סימון ע\"י משתמש" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "סימונים ע\"י משתמש" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "סימון ע\"י %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "תאריך מחיקה" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "מחיקת מודרטור" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "מחיקות מודרטור" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "מחיקת מודרציה ע\"י %r" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "שמך:" + +#: contrib/comments/templates/comments/freeform.html:5 +#: contrib/comments/templates/comments/form.html:27 +msgid "Comment:" +msgstr "תגובה:" + +#: contrib/comments/templates/comments/freeform.html:9 +#: contrib/comments/templates/comments/form.html:32 +msgid "Preview comment" +msgstr "תצוגה מקדימה של התגובה" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "שם משתמש:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "סיסמה:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "שכחת את סיסמתך ?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "יציאה" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "דירוג" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "נדרש" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "אופציונלי" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "שליחת תמונה" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "הדירוג נדרש מאחר והזנת לפחות דרוג אחד אחר." + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"התגובה נשלחה ע\"י משתמש אשר שלח פחות מ-%(count)s " +"תגובה:\n" +"\n" +"%(text)s" +msgstr[1] "" +"התגובה נשלחה ע\"י משתמש אשר שלח פחות מ-%(count)s " +"תגובות:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"ההודעה נשלחה ע\"י משתמש מפוקפק:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "רק פעולות POST מותרות" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "אחד או יותר מהשדות הנדרשים אינו נשלח." + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "מישהו התעסק עם טופס התגובה (הפרת אבטחה)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "טופס התגובה הכיל פרמטר target לא חוקי -- מזהה האובייקט אינו חוקי" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "טופס התגובה לא הכיל 'preview' או 'post'" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "משתמשים אנונימיים אינם יכולים להצביע" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "מזהה תגובה שגוי" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "לא ניתן להצביע לעצמך" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                    By %s:

                                                    \n" +"
                                                      \n" +msgstr "" +"

                                                      ע\"י %s:

                                                      \n" +"
                                                        \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "הכל" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "כל תאריך" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "היום" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "בשבוע האחרון" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "החודש" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "השנה" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "כן" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "לא" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "לא ידוע" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "זמן פעולה" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "מזהה אובייקט" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "ייצוג אובייקט" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "דגל פעולה" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "הערה לשינוי" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "רישום יומן" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "רישומי יומן" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "כל התאריכים" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "הנוכחי." + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "שינוי:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "תאריך:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "שעה:" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/base.html:29 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +msgid "Home" +msgstr "דף הבית" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "תיעוד" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "ייסומניות" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin/base.html:24 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "שינוי סיסמה" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "ייסומוניות תיעוד" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                        To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                        \n" +msgstr "" +"\n" +"

                                                        כדי להתקין ייסומניות, יש לגרור את הקישור לסרגל הסימניות\n" +"שלך, או קליק ימני והוספה לסימניות. כעת ניתן\n" +"לבחור את הייסומניה מכל עמוד באתר. יש לשים לב כי חלק מהייסומניות\n" +"ניתנות לצפיה רק ממחשב שמסווג\n" +"כ\"פנימי\" (יש לדבר עם מנהל המערכת שלך אם אינך בטוח/ה\n" +"שהמחשב מסווג ככזה).

                                                        \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "תיעוד לדף זה" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "מקפיץ אותך מכל עמוד לתיעוד התצוגה שייצרה אותו." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "הצג מזהה אובייקט" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "מציג את סוג התוכן והמזהה הייחודי לעמודים המייצגים אובייקט בודד." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "עריכת אובייקט זה (בחלון הנוכחי)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "קופץ לעמוד הניהול לעמודים אשר מייצגים אובייקטים בודד." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "עריכת אובייקט זה (בחלון חדש)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "כנ\"ל, אך דף הניהול ייפתח בחלון חדש." + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "האם שכחת את הסיסמה שלך?" + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:23 +msgid "Log in" +msgstr "כניסה" + +#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:9 +msgid "Delete" +msgstr "מחיקה" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "שמירה כחדש" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "שמירה והוספת אחר" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "שמירה והמשך עריכה" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "שמירה" + +#: contrib/admin/templates/admin/base.html:24 +msgid "Welcome," +msgstr "שלום" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "סינון" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "בצע" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "תוצאה אחת" +msgstr[1] "%(counter)s תוצאות" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s סה\"כ" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "היסטוריה" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "תאריך/שעה" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "משתמש" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "פעולה" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "l d.m.y H:i:s" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"לאובייקט זה אין היסטוריית שינוי. כנראה לא השתמשו בממשק הניהול הזה להוספתו." + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "הוספה" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "צפיה באתר" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "נא לתקן את השגיאה המופיעה מתחת." +msgstr[1] "נא לתקן את השגיאות המופיעות מתחת." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "מיון" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "מיון:" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "ניהול אתר Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "ניהול Django" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "הצג הכל" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "דף לא קיים" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "אנו מצטערים, לא ניתן למצוא את הדף המבוקש." + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr " לפי %(title)s " + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"מחיקת %(object_name)s '%(object)s' תמחק אובייקטים קשורים, אך לחשבון שלך אין " +"הרשאות למחיקת אובייקטים מהסוג הבא:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"האם ברצונך למחוק את %(object_name)s·\"%(object)s\"? כלהפריטים הקשורים הבאים " +"יימחקו:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "כן, אני בטוח/ה" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"משהו שגוי בהתקנת בסיס הנתונים שלך. נא לוודא שנוצרו טבלאות בסיס הנתונים " +"המתאימות, ובסיס הנתונים ניתן לקריאה על ידי המשתמש המתאים." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "מודלים זמינים ביישום %(name)s." + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "שינוי" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "אין לך הרשאות לעריכה" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "פעולות אחרונות" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "הפעולות שלי" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "לא נמצאו" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "שגיאת שרת" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "שגיאת שרת (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "שגיאת שרת (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"התרחשה שגיאה. היא דווחה למנהלי האתר בדוא\"ל ותתוקן בקרוב. תודה על סבלנותך." + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "הוספת %(name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "הודעה זו התקבלה כי ביקשת איפוס סיסמה" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "עבור חשבון המשתמש שלך ב %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "סיסמתך החדשה: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "ניתן לשנות את הסיסמה בכל עת ע\"י פניה לדף זה:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "שם המשתמש שלך, במקרה ששכחת:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "תודה על השימוש באתר שלנו!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "צוות %(site_name)s" + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "שינוי סיסמה" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"נא להזין את סיסמתך הישנה, למען האבטחה, ולאחר מכן את סיסמתךהחדשה פעמיים כדי " +"שנוכל לוודא שהקלדת אותה כראוי." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "סיסמה ישנה:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "סיסמה חדשה:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "אימות סיסמה:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "שנה את סיסמתי" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "הסיסמה שונתה בהצלחה" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "סיסמתך שונתה." + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "תודה על בילוי זמן איכות עם האתר." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "התחבר/י שוב" + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "איפוס סיסמה" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "הסיסמה אופסה בהצלחה" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"שלחנו את הסיסמה החדשה לכתובת הדוא\"ל שהזנת. היא אמורה להתקבל תוך זמן קצר." + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"שכחת את סיסמתך ? נא להזין את כתובת הדוא\"ל מתחת, אנו נאפסאת הסיסמה ונשלח את " +"החדשה אליך." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "כתובת דוא\"ל:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "אפס את סיסמתי" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:289 +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:297 +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:300 +msgid "Integer" +msgstr "מספר שלם" + +#: contrib/admin/views/doc.py:280 +msgid "Boolean (Either True or False)" +msgstr "בוליאני (אמת או שקר)" + +#: contrib/admin/views/doc.py:281 contrib/admin/views/doc.py:299 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "מחרוזת (עד %(maxlength)s תווים)" + +#: contrib/admin/views/doc.py:282 +msgid "Comma-separated integers" +msgstr "מספרים שלמים מופרדים בפסיקים" + +#: contrib/admin/views/doc.py:283 +msgid "Date (without time)" +msgstr "תאריך (ללא שעה)" + +#: contrib/admin/views/doc.py:284 +msgid "Date (with time)" +msgstr "תאריך (כולל שעה)" + +#: contrib/admin/views/doc.py:285 +msgid "E-mail address" +msgstr "כתובת דוא\"ל" + +#: contrib/admin/views/doc.py:286 contrib/admin/views/doc.py:287 +#: contrib/admin/views/doc.py:290 +msgid "File path" +msgstr "נתיב קובץ" + +#: contrib/admin/views/doc.py:288 +msgid "Decimal number" +msgstr "מספר עשרוני" + +#: contrib/admin/views/doc.py:294 +msgid "Boolean (Either True, False or None)" +msgstr "בוליאני (אמת, שקר או כלום)" + +#: contrib/admin/views/doc.py:295 +msgid "Relation to parent model" +msgstr "יחס למודל אב" + +#: contrib/admin/views/doc.py:296 +msgid "Phone number" +msgstr "מספר טלפון" + +#: contrib/admin/views/doc.py:301 +msgid "Text" +msgstr "טקסט" + +#: contrib/admin/views/doc.py:302 +msgid "Time" +msgstr "זמן" + +#: contrib/admin/views/doc.py:303 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:304 +msgid "U.S. state (two uppercase letters)" +msgstr "מדינה בארה\"ב (שתי אותיות גדולות)" + +#: contrib/admin/views/doc.py:305 +msgid "XML text" +msgstr "טקסט XML" + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "ניהול אתר" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "הוספת %(name)s \"%(obj)s\" בוצעה בהצלחה." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "ניתן לערוך שוב מתחת" + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "ניתן להוסיף %s נוסף מתחת." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "הוספת %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "%s התווסף." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "ו" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "%s שונה." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "%s נמחק." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "אף שדה לא השתנה." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "שינוי %(name)s \"%(obj)s\" בוצע בהצלחה." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "הוספת %(name)s \"%(obj)s\" בוצעה בהצלחה. ניתן לערוך אותו שוב מתחת." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "שינוי %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "אחד או יותר %(fieldname)s ב%(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "אחד או יותר %(fieldname)s ב%(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "מחיקת %(name)s \"%(obj)s\" בוצעה בהצלחה." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "האם את/ה בטוח/ה ?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "היסטוריית שינוי: %s" + +#: contrib/admin/views/main.py:567 +#, python-format +msgid "Select %s" +msgstr "בחירת %s" + +#: contrib/admin/views/main.py:567 +#, python-format +msgid "Select %s to change" +msgstr "בחירת %s לשינוי" + +#: contrib/admin/views/main.py:743 +msgid "Database error" +msgstr "שגיאת בסיס נתונים" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:43 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"נא להזין שם משתמש וסיסמה נכונים. בשני השדות גודל האותיות האנגליות משנה." + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"נא להתחבר שוב, מאחר ופג תוקף ההתחברות הנוכחית. אל דאגה: המידע ששלחת נשמר." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"נראה שהדפדפן שלך אינו מוגדר לקבל עוגיות. נא לאפשר עוגיות, לטעון מחדש את הדף " +"ולנסות שוב." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "שם משתמש אינו יכול להכיל את התו '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "כתובת הדוא\"ל שלך אינה שם המשתמש שלך. נסה/י '%s' במקום." + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "הפניה מ" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "זה אמור להיות נתיב מלא, ללא שם המתחם. לדוגמא: '‎/events/search'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "הפניה אל" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "יכול להיות נתיב מלא (כנ\"ל) או URL מלא המתחיל ב'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "הפניה" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "הפניות" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "נראה שעוגיות לא מאופשרות בדפדפן שלך.הן נדרשות כדי להתחבר." + +#: contrib/auth/forms.py:45 +msgid "This account is inactive." +msgstr "חשבון זה אינו פעיל." + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "יצאת מהמערכת" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "שם" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "שם קוד" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "הרשאה" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "הרשאות" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "קבוצה" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "קבוצות" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "שם משתמש" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "שם פרטי" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "שם משפחה" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "כתובת דוא\"ל" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "סיסמה" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "השתמש ב '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "סטטוס איש צוות" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "מציין האם המשתמש יכול להתחבר לאתר הניהול." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "פעיל" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "סטטוס משתמש על" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "כניסה אחרונה" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "תאריך הצטרפות" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"בנוסף לכל ההרשאות שהוקצו ידנית, יוענקו למשתמש גם כל ההרשאות של כל קבוצה " +"המשוייכת אליו." + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "הרשאות משתמש" + +#: contrib/auth/models.py:70 +msgid "user" +msgstr "משתמש" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "משתמשים" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "מידע אישי" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "הרשאות" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "תאריכים חשובים" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "קבוצות" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "הודעה" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"לדוגמא: '/about/contact/'. יש לוודא הימצאות הקווים הנטויים בהתחלה ובסוף." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "כותרת" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "תוכן" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "אפשר תגובות" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "שם תבנית" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"דוגמא: 'flatpages/contact_page'. האם אינו קיים, המערכתתשתמש ב 'flatpages/" +"default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "הרשמה נדרשת" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "אם זה מסומך, רק משתמשים מחוברים יוכלו לצפות בדף." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "דף פשוט" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "דפים פשוטים" + diff --git a/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..25395987703dfde0969791a4a650ee843bd8dc25 GIT binary patch literal 1626 zcwTjqO>Y}T7{`ai4m7+J!i$6i(nzR+g4Vc+s3H?y(ll0E>ZXcaDvp!gslCa1$C{l@ zu|VPgcVe(fELF%GM%<_P*qNasOS6@Bn>QLZm z@ROX2_jzW&13m?gGyatM-OKjB=3M;W!B4@zvi(PS7yq-otFx1L_8ssU@GiIv{s>+G zA7tn6OgaBcQ!f7sXhv*$f0mp)<~!uce;-rVlf=!(^g2UaFDCB8SQv?|$;5wxxH_KB z?wVYWX4>>c&Qn%s7RX`{ki|_FMr3z5m{;ok#NeW3Y+|R7ukb^ z$M#CZnRK>D_#5O3i&G}sw9MC}6%{7^23>5)FoJH=D{0K}irREBt*42iRo+s(xyB`3 z@s%*VCN`bnBKNHzl`x3IdP9+GLd1C8Cab&&FH)ryTBA3y%Ct(VJmS8hZIMdqH$>?3 zSCX@o#R2uB(BGh>nxZ|_BrIwvPYiGJAa<(ONTn5J85&fXN+krT3HNRaDUtmHo+xe) zu1m3woZjWYd!NfB6mf|L>nyH|^@N@)6sK2tOGxEal6n|;7t(s-)kKL-Utg(tOOi8% z8{S2xc!|yyi|4)KoHtjb=jKYY^RE?Qfupy|H^b!r{N~3cG4@F=7UP3+!nZY1KCD)^!hMsMRrTu)h@do|9QC3*>8jNhKFT&^siv|>6~60zbj zg11}9uK0)Q^;X2f_)W~1$%L!7Zq&-&{E43_;ue?Qa_oyBjO!&jzZNR|>U0NxJ^ig7 z6WxWltM_%6oJpwuN)L5k@45{ zCp*-(vjg9b39@AyHHF*lo)y{Im-@g-f;taeod+n-Wb5lY1a*##t&5Oj(`EXx!n%4fgd`9sd|8%J=5O^ogp-w&FQWic;8@PVjDTO(;peR bZNJc=b2t_qSEiW<${w1rn~@(unlJVb-NG_x literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..75b53dd --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/he/LC_MESSAGES/djangojs.po @@ -0,0 +1,111 @@ +# Hebrew translation of djangojs. +# Copyright (C) 2006 THE djangojs'S COPYRIGHT HOLDER +# This file is distributed under the same license as the djangojs package. +# Meir Kriheli , 2006. +# +# +msgid "" +msgstr "" +"Project-Id-Version: djangojs 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-03-30 13:28+0200\n" +"PO-Revision-Date: 2006-03-30 13:35+0200\n" +"Last-Translator: Meir Kriheli \n" +"Language-Team: Hebrew\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"ינואר פברואר מרץ אפריל מאי יוני יולי אוגוסט ספטמבר אוקטובר נובמבר דצמבר" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "ראשון שני שלישי רביעי חמישי שישי שבת" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s זמינות" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "בחירת הכל" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "הוספה" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "הסרה" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s נבחרות" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "יש לסמן את ההרשאות המבוקשות וללחוץ על " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "איפוס הכל" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "ר ש ש ר ח ש ש" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "כעת" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "שעון" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "בחירת שעה" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "חצות" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 בבוקר" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "צהריים" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "ביטול" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "היום" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "לוח שנה" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "אתמול" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "מחר" + diff --git a/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..797b87b3325c42268d36ba0a3e840b14901012c9 GIT binary patch literal 30545 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 SLO&SDRu%%vu9xkUEB+IFxYlO? literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.po new file mode 100644 index 0000000..6bdfd5c --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/django.po @@ -0,0 +1,2022 @@ +# translation of django.po to +# translation of django.po to +# translation of django.po to +# This file is distributed under the same license as the PACKAGE package. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER. +# Nagy Károly , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:14+0200\n" +"PO-Revision-Date: 2006-05-10 12:13+0200\n" +"Last-Translator: Nagy Károly \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.9.1\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "objektum ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "címsor" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "megjegyzés" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "besorolás #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "besorolás #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "besorolás #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "besorolás #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "besorolás #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "besorolás #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "besorolás #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "besorolás #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "érvényes besorolás" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "dátum/idő beállítva" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "publikus" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "IP szám" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "eltávolítva" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Jelöld be a négyzetet, ha a megjegyzés nem megfelelő. Az \"Ezt a megjegyzést " +"törölték\" üzenet fog megjelenni helyette." + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "megjegyzés" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Tartalom objektum" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Beküldte %(user)s %(date)s -kor\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "személy neve" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP cím" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "személyzet által elfogadva" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "Szabad megjegyzés" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "Szabad megjegyzések" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "értékelés" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "értékelés dátuma" + +#: contrib/comments/models.py:237 +#, fuzzy +msgid "karma score" +msgstr "Karma értékelés" + +#: contrib/comments/models.py:238 +#, fuzzy +msgid "karma scores" +msgstr "Karma értékelése" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d értékelés %(user)s -tól" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Ezt a megjegyzést %(user)s jelölte meg:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "jelölés dátuma" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "Felhasználó jelölése" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "Felhasználó jelölései" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Megjelölte %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "törlés dátuma" + +#: contrib/comments/models.py:280 +#, fuzzy +msgid "moderator deletion" +msgstr "Moderátor törlése" + +#: contrib/comments/models.py:281 +#, fuzzy +msgid "moderator deletions" +msgstr "Moderátor törlései" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Moderátor törlés %r által" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Ismeretlen felhasználó nem szavazhat" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Érvénytelen megjegyzés ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Nem szavazhatsz magadra" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Ez az értékelés szükséges, mert legalább még egy értékelés kell." + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Ezt a megjegyzést olyan felhasználó küldte akinek kevesebb mint %(count)s " +"megjegyzése van:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Ezeket a megjegyzéseket olyan felhasználó küldte akinek kevesebb mint %" +"(count)s megjegyzése van:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Ezt a megjegyzést egy nem értékelt felhasználó küldte:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Csak POST engedélyezett" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Egy vagy több kötelező mező nincs kitöltve" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Valaki megváltoztatta a megjegyzés űrlapot (biztonság megsértése)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"A megjegyzés űrlap érvénytelen 'target' paramétert tartalmaz -- az objektum " +"ID-je érvénytelen volt" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "A megjegyzés űrlap nem biztosít sem 'preview' -t sem 'post' -ot." + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Felhasználó:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Jelszó:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Elfelejtetted a jelszavad?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Kijelentkezés" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Értékelések" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Kötelező" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcionális" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Fénykép beküldése" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Megjegyzés:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Megjegyzés előnézete" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Neved:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                        By %s:

                                                        \n" +"
                                                          \n" +msgstr "" +"

                                                          Szerző %s:

                                                          \n" +"
                                                            \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Mind" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Bármely dátum" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Ma" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Utolsó 7 nap" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Ez a hónap" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Ez az év" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Igen" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nem" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Ismeretlen" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "művelet időpontja" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "objektum id" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "objektum repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "művelet jelölés" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "üzenet megváltoztatása" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "naplóbejegyzés" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "naplóbejegyzések" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Minden dátum" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Kérlek írd be a helyes felhasználónevet és jelszót. Mindkét mező kisbetű-" +"nagybetű érzékeny." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Bejelentkezés" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Kérlek jelentkezz be újra, mert a munkameneted végetért. Ne aggódj, minden " +"beküldött adatod el van mentve." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Úgy tűnik a böngésződön nincs engedélyezve a cookie-k fogadása. Kérlek " +"engedélyezd és töltsd újra az oldalt!" + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "A felhasználónév nem tartalmazhat '@' karaktert." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Az email címed nem a felhasználóneved. Próbáld a(z) '%s' inkább." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Honlap karbantartás" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "A(z) %(name)s \"%(obj)s\" sikeresen hozzáadva." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Alább ismét szerkesztheted." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Alább hozzáadhatsz egy másik %s -t." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "%s hozzáadása" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "%s hozzáadva." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "és" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "%s megváltoztatva." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "%s törölve." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Egy mező sem változott." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "A(z) %(name)s \"%(obj)s\" sikeresen megváltoztatva." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"A(z) %(name)s \"%(obj)s\" sikeresen hozzáadva. Alább ismét szerkesztheted." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "%s megváltoztatása" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Egy vagy több %(fieldname)s a(z) %(name)s -ban: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Egy vagy több %(fieldname)s a(z) %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "A(z) %(name)s \"%(obj)s\" sikeresen törölve." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Biztos vagy benne?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Változások története: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Kiválasztás %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Válaszd a(z) %s a változtatáshoz" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Egész" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Logikai (True vagy False)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Karakterlánc (%(maxlength)s hosszig)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Vesszővel elválasztott egészek" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Dátum (idő nélkül)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Dátum (idővel)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "E-mail cím" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Elérési út" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Tizes számrendszerű szám" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Logikai (True, False vagy None)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Szülőkapcsolat" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Telefonszám" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Szöveg" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Idő" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "USA állam (két nagybetű)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "XML szöveg" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dokumentáció" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Jelszó megváltoztatása" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Kezdőlap" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Történet" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dátum/idő" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Felhasználó" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Művelet" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Az objektumnak nincs változási története. Valószínűleg nem ezen a " +"karbantartó oldalon lett rögzítve." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django honlap adminisztráció" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django adminisztráció" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Szerver hiba" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Szerver hiba (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Szerver hiba (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Hiba történt, melyet e-mailben jelentettünk az oldal karbantartójának. A " +"rendszer remélhetően hamar megjavul, köszönjük a türelmedet." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Nincs ilyen oldal" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Sajnáljuk, a kért oldalt nem találjuk." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Hozzáadás" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Változtatás" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Nincs jogod szerkeszteni." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Utóbbi műveletek" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Az én műveleteim" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nincs elérhető" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s hozzáadása" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Csak nem elfelejtetted a jelszavadat?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Üdvözöllek," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Törlés" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"A(z) %(object_name)s '%(object)s' törlése a kapcsolódó objektumok törlését " +"is eredményezi, de a hozzáférésed nem engedi a következő típusú objektumok " +"törlését:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Biztos hogy törlöd a(z) %(object_name)s \"%(object)s\"? A összes következő " +"kapcsolódó elem is törlődik:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Igen, biztos vagyok benne" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr " Szerző %(title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Mehet" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Megtekintés a honlapon" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Kérlek javítsd az alábbi hibát." +msgstr[1] "Kérlek javítsd az alábbi hibákat." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Rendezés" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Rendezettség:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Mentés újként" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Mentés és másik hozzáadása" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Mentés és a szerkesztés folytatása" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Mentés" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Jelszó változtatása" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Sikeres jelszóváltoztatás" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Jelszavad megváltozott." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Jelszó törlés" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Elfelejtetted a jelszavad? Írd be az e-mail címed, mi töröljük a jelszavad " +"és az újat e-mailben elküldjük neked." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-mail cím:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Jelszavam törlése" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Köszönjük hogy egy kis időt töltöttél a honlapunkon." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Jelentkezz be újra" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Sikeres jelszótörlés" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "Új jelszavadat elküldtük e-mailben. Hamarosan meg kell kapnod." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Írd be a régi jelszavad biztonsági okokból, majd az újat kétszer, hogy " +"biztosan ne gépeld el." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Régi jelszó:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Új jelszó:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Jelszó megerősítése:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Jelszavam megváltoztatása" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Ezt az e-mail-t azért kaptad, mert jelszótörlést kértél" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "hozzáférésedhez a következő honlapon: %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Új jelszavad: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Bármikor megváltoztathatod a jelszavad a következő oldalon:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Felhasználóneved, ha elfelejtetted volna:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Köszönjük, hogy használtad honlapunkat!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "A %(site_name)s csapata" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Könyvjelzők" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Dokumentum könyvjelzők" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                            To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                            \n" +msgstr "" +"\n" +"

                                                            A könyvjelzők felvételéhez húzd a könyvjelzők linkjét az " +"eszköztárra, vagy kattints rájuk jobb egérgombot és úgy add hozzá. Ezután \n" +"már ki tudod választani a könyvjelzőt a honlap bármely oldaláról. A " +"könyvjelzők között néhány oldal csak 'belső' gépekről nézhető meg.\n" +"(Beszélj a rendszergazdával hogy a te géped 'belső' gép-e.).

                                                            \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Az oldal dokumentációja" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Bármely oldalról annak a nézetnek a dokumentációjára ugrik, mely a kérdéses " +"oldalt generálta." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Az objektum ID kijelzése" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Az objektum által reprezentált oldalak 'content-type' és 'unique ID' " +"értékeit mutatja." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Objektum szerkesztése (aktuális ablakban)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Az objektumhoz tartozó oldalak adminisztrációjához ugrik." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Objektum szerkesztése (új ablakban)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Mint fentebb, de az adminisztrációs oldalt új ablakban nyitja." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Dátum:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Idő:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Éppen:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Változás:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "átirányítva innen" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Ennek abszolút elérési útnak kell lennie, a domén név nélkül. Példa: '/" +"events/search/'" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "átirányítva ide" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Ennek vagy abszolút elérési útnak kell lennie (mint fentebb) vagy teljes URL-" +"nek 'http://' -vel kezdve." + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "átirányítás" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "átirányít" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Például: '/about/contact/'. Figyelj a nyitó és záró perjelre!" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "cím" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "tartalom" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "megjegyzések engedélyezése" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "sablon neve" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Példa: 'flatpages/contact_page'. Ha ez nem létezik, a rendszer a 'flatpages/" +"default' -ot használja." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "regisztráció szükséges" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Ha ez be van jelölve, csak bejelentkezett felhasználó tudja az oldalt " +"megnézni." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "egyszerű oldal" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "egyszerű oldalak" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "név" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "kódnév" + +#: contrib/auth/models.py:17 +#, fuzzy +msgid "permission" +msgstr "Engedély" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +#, fuzzy +msgid "permissions" +msgstr "Engedélyek" + +#: contrib/auth/models.py:29 +#, fuzzy +msgid "group" +msgstr "Csoport" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +#, fuzzy +msgid "groups" +msgstr "Csoportok" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "felhasználónév" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "keresztnév" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "vezetéknév" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "e-mail cím" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "jelszó" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Használat '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "Személyzet státusa" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "" +"Megadja hogy a felhasználó bejelentkezhet-e erre az adminisztrációs oldalra." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "aktív" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "rendszergazda státusz" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "utolsó bejelentkezés" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "csatlakozás dátuma" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"A kézzel beállított jogosultságok mellett a felhasználó a csoportjának a " +"jogait is megkapja." + +#: contrib/auth/models.py:67 +#, fuzzy +msgid "user permissions" +msgstr "Engedélyek" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "Felhasználó" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "Felhasználók" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Személyes információ" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Engedélyek" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Fontos dátumok" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Csoportok" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "Üzenet" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"A böngésződ úgy tűnik nem támogatja a cookie-kat. A cookie-k engedélyezése " +"szükséges a bejelentkezéshez." + +#: contrib/contenttypes/models.py:25 +#, fuzzy +msgid "python model class name" +msgstr "python modul név" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "tartalom típusa" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "tartalom típusok" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "munkamenet kulcs" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "munkamenet adat" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "lejárat dátuma" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "munkamenet" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "munkamenetek" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domén neve" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "megjelenő név" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "honlap" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "honlapok" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Hétfő" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Kedd" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Szerda" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Csütörtök" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Péntek" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Szombat" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Vasárnap" + +#: utils/dates.py:14 +msgid "January" +msgstr "Január" + +#: utils/dates.py:14 +msgid "February" +msgstr "Február" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Március" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Április" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Május" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Június" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Július" + +#: utils/dates.py:15 +msgid "August" +msgstr "Augusztus" + +#: utils/dates.py:15 +msgid "September" +msgstr "Szeptember" + +#: utils/dates.py:15 +msgid "October" +msgstr "Október" + +#: utils/dates.py:15 +msgid "November" +msgstr "November" + +#: utils/dates.py:16 +msgid "December" +msgstr "December" + +#: utils/dates.py:19 +#, fuzzy +msgid "jan" +msgstr "és" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +#, fuzzy +msgid "may" +msgstr "nap" + +#: utils/dates.py:19 +msgid "jun" +msgstr "" + +#: utils/dates.py:20 +msgid "jul" +msgstr "" + +#: utils/dates.py:20 +msgid "aug" +msgstr "" + +#: utils/dates.py:20 +msgid "sep" +msgstr "" + +#: utils/dates.py:20 +msgid "oct" +msgstr "" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Aug." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Szept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dec." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "év" +msgstr[1] "évek" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "hónap" +msgstr[1] "hónapok" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "nap" +msgstr[1] "napok" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "óra" +msgstr[1] "órák" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "perc" +msgstr[1] "percek" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengáli" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Cseh" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Walesi" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Dán" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Német" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Angol" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Spanyol" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Francia" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Gall" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Izlandi" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Olasz" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japán" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Holland" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norvég" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brazil" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Román" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Orosz" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Szlovák" + +#: conf/global_settings.py:58 +#, fuzzy +msgid "Slovenian" +msgstr "Szlovák" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Szerb" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Svéd" + +#: conf/global_settings.py:61 +#, fuzzy +msgid "Ukrainian" +msgstr "Brazil" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Egyszerű kínai" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Hagyományos kínai" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Az érték csak betűket, számokat és alulvonást tartalmazhat." + +#: core/validators.py:64 +#, fuzzy +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Az érték csak betűket, számokat, alulvonást és perjelet tartalmazhat." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Nagybetűk itt nem megengedettek." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Kisbetűk itt nem megengedettek." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Csak számokat adj meg, vesszőkkel elválasztva." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Érvényes e-mail címeket adja meg, vesszőkkel elválasztva." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Írj be egy érvényes IP címet." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Üres érték itt nem megengedett." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Nem szám karakterek itt nem megengedettek." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Ez az érték nem tartalmazhat kizárólag számokat." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Adj meg egy egész számot." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Itt csak betűk megengedettek." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Írj be egy érvényes dátumot 'ÉÉÉÉ-HH-NN' alakban." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Írj be egy érvényes időt 'ÓÓ:PP' alakban." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Írj be egy érvényes dátumot/időt 'ÉÉÉÉ-HH-NN ÓÓ-PP' alakban." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Írj be egy érvényes e-mail címet." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Tölts fel egy érvényes képfájlt. A feltöltött fájl vagy nem kép volt vagy " +"megsérült." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "A(z) %s URL nem érvényes képfájlra mutat." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"A telefonszámoknak 'XXX-XXX-XXXX' formátumúnak kell lennie. \"%s\" " +"érvénytelen." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "A(z) %s URL nem érvényes QuickTime videóra mutat." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Érvényes URL szükséges." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Érvényes HTML kell. A hiba:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Rosszul formázott XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "Érvénytelen URL: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "A(z) %s URL egy rossz link." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Írj be egy érvényes USA állam rövidítést." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Vigyázz a szádra! Az ilyen szavak (%s) itt nem megengedettek." +msgstr[1] "Vigyázz a szádra! Az ilyen szavak (%s) itt nem megengedettek." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "A mezőnek egyeznie kell a(z) %s mezővel." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Írj be valamit legalább egy mezőbe." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Töltsd ki mindkét mezőt vagy hagyd üresen mindkettőt." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Ezt a mezőt meg kell adni, ha %(field)s értéke %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Ezt a mezőt meg kell adni, ha %(field)s értéke nem %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Ugyanazok az értékek nem megengedettek." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Az értéknek %s hatványának kell lennie." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Írj be egy érvényes tizes számrendszerű számot." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Írj be egy legalább %s jegyű érvényes tizes számrendszerű számot." +msgstr[1] "Írj be egy legalább %s jegyű érvényes tizes számrendszerű számot." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Írj be egy érvényes tizes számrendszerű számot, legfeljebb %s tizedessel." +msgstr[1] "" +"Írj be egy érvényes tizes számrendszerű számot, legfeljebb %s tizedessel." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "A feltöltött fájlod legalább %s bájt méretű legyen." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "A feltöltött fájlod legfeljebb %s bájt méretű legyen." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Ennek a mezőnek a formátuma rossz." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "A mező érvénytelen." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Nem lehet semmit kinyerni %s -ból." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"A(z) %(url)s URL érvénytelen Content-Type fejlécet adott vissza '%" +"(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Zárd le a nyitott %(tag)s címkét a %(line)s sorban. (A sor kezdete: \"%" +"(start)s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Valamely szöveg a(z) %(line)s sorban nem megengedett ebben a környezetben. " +"(A sor kezdete: \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" a(z) %(line)s sorban érvénytelen tulajdonság. (A sor kezdete: " +"\"%(start)s\".)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" a(z) %(line)s sorban érvénytelen címke. (A sor kezdete: \"%" +"(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"A %(line)s sorban lévő címkéről hiányzik egy vagy több kötelező tulajdonság. " +"(A sor kezdete: \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"A(z) \"%(attr)s\" jellemző a %(line)s sorban érvénytelen. (A sor kezdete: \"%" +"(start)s\".)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s ezzel a(z) %(type)s már létezik az adott %(field)s -nél." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s ezzel a(z) %(fieldname)s már létezik." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Kötelező mező." + +#: db/models/fields/__init__.py:337 +#, fuzzy +msgid "This value must be an integer." +msgstr "Az értéknek %s hatványának kell lennie." + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "Az értéknek %s hatványának kell lennie." + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "A mező érvénytelen." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Adj meg egy érvényes fájlnevet." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Írj be érvényes %s -t." + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr "Az ID-ket vesszőkkel válaszd el." + +#: db/models/fields/related.py:581 +#, fuzzy +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Tartsd lenyomva a \"Control\"-t (vagy Mac-en a \"Command\"-ot) több elem " +"kiválasztásához." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Kérlek írj be érvényes %(self)s ID-t. A mostani érték %(value)r érvénytelen." +msgstr[1] "" +"Kérlek írj be érvényes %(self)s ID-ket. A mostani értékek %(value)r " +"érvénytelenek." + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "A szövegnek kevesebbnek kell lennie %s karakternél." +msgstr[1] "A szövegnek kevesebbnek kell lennie %s karakternél." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Sortörések itt nem megengedettek." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Válassz érvényes elemet, '%(data)s' nincs a(z) %(choices)s között." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "A küldött fájl üres." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Írj be egy számot -32,768 és 32,767 között." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Írj be egy pozitív számot." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Írj be egy egész számot 0 and 32,767 között." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "igen, nem, talán" + +#~ msgid "Comment" +#~ msgstr "Megjegyzés" + +#~ msgid "Comments" +#~ msgstr "Megjegyzések" + +#~ msgid "String (up to 50)" +#~ msgstr "Karakterlánc (50 karakterig)" + +#~ msgid "label" +#~ msgstr "címke" + +#~ msgid "package" +#~ msgstr "csomag" + +#~ msgid "packages" +#~ msgstr "csomagok" diff --git a/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/hu/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..5b8214fa31f59d599bfd868035fc90d0ef4906d9 GIT binary patch literal 1556 zcwTLiPiSLB9LLADsW$7bTK|Bc@B>-2EH5U7Ep4pJcH72oyJ<_4?dnM;?@eD`GMNc8 z^P-6!^eF6Q5j}Vis;44?va;Zxi#!%q@a)m6;7Lz<@Zfjyf~DZV%;z^VzrQoTU*~5& zVi?b0K8N`;=JS~EKE%f34{#Rz6PyPB1|J3g0UrV9rtZ!gOxGFg=L_?jfhu>D=!2On(2# zOuqjdd>H(C!f(N+z#qZK!C%0~z~8_pz`thlyyg(>P4Ge?w^N1ud@$iV;5qQULT+~( zTmU}>tKb6=DYNO>ysp^^&tvgV?0r1VPKW=80y{f=UOq?vKEBp@me=I#^Er9{{!hH$ z3oNhe$%(E1i*pm%=f{?amwF|(7Da4rz!S+^QqV=q*0~fa;s$~Wg=Fh(t*szVF6oK? zc5-}Sg&Lbk!A*W9b=YOwT&3I$sU})xEY!IP+qBj(RZTP z5{C9dr#*M|AV1g;;aFhxBvMJ-c5GK`h2<@FAbQYZ4XRUh;4U=~SX1X~PaA{iZwTwe z_+sDa4q^t^qu@{&n`l*~sKZsPJGOWr`r5dlZsQ~huBEXJnz~BG{k>+eZUlGG4mP+G zRaz{UF9(&ypu9qrO7(K(Qn^wtLoqlIgT$WETME2td91f`sf?524Q^e~G+bH9oi

                                                            T@Q-|jO))6whC)q^{d628R0cY9A7O!E+W%mr+ zTfwpq5z}`toaY_vg*uinNiAjX`z}tbKj~nF@q8)gmbxZr8K3g1h@eYpA=RMICj*vEWxpRqfmK}17R2A>j tR(9f!@K!q_ihNpVRXEIk@TFpQX+Jx`7j=DrUW?, 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: djangojs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-05-10 11:59+0200\n" +"Last-Translator: Nagy Károly \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.9.1\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Elérhető %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Mindent kijelöl" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Hozzáad" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Eltávolít" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s kiválasztva" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Válaszd ki a kért elemeket és kattints" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Összes törlése" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "Január Február Március Április Május Június Július Szeptember Október November December" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Vasárnap Hétfő Kedd Szerda Csütörtök Péntek Szombat" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "V H K Sz Cs P Szo" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Most" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Óra" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Válaszd ki az időt" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Éjfél" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "Reggel 6 óra" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Dél" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Mégsem" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Ma" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Naptár" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Tegnap" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Holnap" + diff --git a/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..6efae42fbc036ad77be66405ed07998d6f4b6b0d GIT binary patch literal 29355 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�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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.po new file mode 100644 index 0000000..70b4368 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/django.po @@ -0,0 +1,2042 @@ +# translation of Django. +# Copyright (C) 2006 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Dagur Páll Ammendrup , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:13+0200\n" +"PO-Revision-Date: 2006-05-11 00:02+0000\n" +"Last-Translator: Dagur Páll Ammendrup \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "kenni hlutar" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "fyrirsögn" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "athugasemd" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "einkunn #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "einkunn #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "einkunn #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "einkunn #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "einkunn #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "einkunn #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "einkunn #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "einkunn #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "er gild einkunn" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "innsent dags/tími" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "er öllum sýnilegt" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "IP tala" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "hefur verið fjarlægt" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Hakaðu við þennan reit ef athugasemdin er óviðeigandi. Skilaboðin „Þessi " +"athugasemd hefur verið fjarlægð“ birtist í staðinn." + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "athugasemd" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +#, fuzzy +msgid "Content object" +msgstr "Efnishlutur" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"%(user)s sendi inn %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nafn einstaklings" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP tala" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "samþykkt af umsjónarmönnum" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "Frjáls athugasemd" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "Frjálsar athugasemdir" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "stig" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "dagsetning stigagjafar" + +#: contrib/comments/models.py:237 +#, fuzzy +msgid "karma score" +msgstr "Karma stig" + +#: contrib/comments/models.py:238 +#, fuzzy +msgid "karma scores" +msgstr "Karma stig" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d stig frá %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Þessi athugasemd var veifuð af %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "Dagsetning veifu" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "Notandaveifa" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "Notendaveifur" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Veifað af %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "eytt dags." + +#: contrib/comments/models.py:280 +#, fuzzy +msgid "moderator deletion" +msgstr "Eytt af umsjónarmanni" + +#: contrib/comments/models.py:281 +#, fuzzy +msgid "moderator deletions" +msgstr "Eytt af umsjónarmönnum" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Eytt af umsjónarmanni %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Ónafngreindir notendur geta ekki kosið" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Ógilt kenni á athugasemd" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Þú getur ekki kosið sjálfa(n) þig" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"Þessi stigagjöf er nauðsynleg vegna þess að þú gafst að minnsta kosti eina " +"aðra einkunn." + +#: contrib/comments/views/comments.py:112 +#, fuzzy, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Þessi athugasemd var send inn af vafasömum notanda:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Þessi athugasemd var send inn af vafasömum notanda:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Þessi athugasemd var send inn af vafasömum notanda:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Eingöngu POST eru leyfð" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Einn eða fleiri nauðsynlegir reitir voru ekki fylltir" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Einhver átti við athugasemdaformið (öryggisbrot)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"Athugasemdaformið hafði ógildan 'target' stika -- kenni hlutarins var ógilt" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Athugasemdaformið útvegaði ekki annað hvort 'forskoða' eða 'senda'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Notandanafn:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Lykilorð:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Gleymdir þú lykilorðinu þínu?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Skrá út" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Einkunnir" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Nauðsynlegt" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Valfrjálst" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Sendu inn ljósmynd" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Athugasemd:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Forskoða athugasemd" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Nafnið þitt:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                            By %s:

                                                            \n" +"
                                                              \n" +msgstr "" +"

                                                              Frá %s:

                                                              \n" +"
                                                                \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Allt" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Allar dagsetningar" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Dagurinn í dag" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Síðustu 7 dagar" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Þessi mánuður" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Þetta ár" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Já" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nei" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Óþekkt" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "tími aðgerðar" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "kenni hlutar" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "framsetning hlutar" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "aðgerðarveifa" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "breyta skilaboði" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "kladdafærsla" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "kladdafærslur" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Allar dagsetningar" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Vinsamlegast sláðu inn rétt notandanafn og lykilorð. Athugaðu að báðir " +"reitirnir þurfa að vera stafréttir." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Skrá inn" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Vinsamlegast skráðu þig inn aftur vegna þess að setan þín rann út. Engar " +"áhyggjur, breytingin þín var vistuð." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Vafri þinn virðist ekki vera stilltur til að leyfa dúsur (cookies). " +"Vinsamlegast gerðu dúsur virkar, endurhladdu þessa síðu og reyndu aftur." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Notendanöfn geta ekki innihaldið '@' merkið." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"Tölvupóstfangið þitt er ekki notandanafnið þitt. Prófaðu '%s' í staðinn." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Vefstjórn" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s „%(obj)s“ var bætt við." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Þú getur breytt því aftur að neðan." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Þú getur bætt öðru %s við að neðan." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Bæta við %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Bætti við %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "og" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Breytti %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Eyddi %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Engum reitum breytt." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s „%(obj)s“ hefur verið breytt." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"%(name)s „%(obj)s“ hefur verið bætt við. Þú getur breytt því aftur að neðan." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Breyta %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Eitt eða fleiri %(fieldname)s í %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Eitt eða fleiri %(fieldname)s í %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s „%(obj)s“ var eytt." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Ertu viss?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Breytingarsaga: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Veldu %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Veldu %s til að breyta" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Heiltala" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Boole-gildi (True eða False)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Strengur (mest %(maxlength)s)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Kommuaðgreindar heiltölur" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Dagsetning (án tíma)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Dagsetning (með tíma)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Tölvupóstfang" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Slóð" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Tugatala" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Boole-gildi (True, False eða None)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Tengsl við yfirlíkan" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Símanúmer" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Texti" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Tími" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "Veffang" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Bandarískt fylki (tveir hástafir)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "XML texti" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Skjölun" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Breyta lykilorði" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Heim" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Saga" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dagsetning/tími" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Notandi" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Aðgerð" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Þessi hlutur hefur enga breytingasögu. Hann var líklega ekki búinn til á " +"þessu stjórnunarsvæði." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django vefstjóri" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django vefstjórn" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Kerfisvilla" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Kerfisvilla (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Kerfisvilla (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Villa hefur komið upp. Hún hefur verið tilkynnt vefstjórunum með tölvupósti " +"og verður örugglega löguð fljótlega. Við þökkum þolinmæðina." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Síða fannst ekki" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Því miður fannst umbeðin síða ekki." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Bæta við" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Breyta" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Þú hefur ekki réttindi til að breyta neinu" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Nýlega framkvæmdar aðgerðir" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Aðgerðir mínar" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Engin fáanleg" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Bæta við %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Gleymdir þú lykilorðinu þínu?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Velkomin(n)," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Eyða" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Eyðing á %(object_name)s „%(object)s“ hefði í för með sér eyðingu á tengdum " +"hlutum en þú hefur ekki réttindi til að eyða eftirfarandi hlutum:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Ertu viss um að þú viljir eyða %(object_name)s „%(object)s“? Öllu " +"eftirfarandi verður eytt:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Já ég er viss." + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "Eftir %(title)s" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Áfram" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Skoða á vef" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Vinsamlegast leiðréttu villuna hér að neðan:" +msgstr[1] "Vinsamlegast leiðréttu villurnar hér að neðan:" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Röðun" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Röð:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Vista sem nýtt" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Vista og búa til aðra" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Vista og halda áfram að breyta" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Vista" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Breyta lykilorði" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Breyting á lykilorði tókst" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Lykilorði þínu var breytt" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Endurstilla lykilorð" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Gleymdir þú lykilorðinu þínu? Sláðu tölvupóstfangið þitt inn að neðan og við " +"munum endurstilla lykilorðið þitt og senda til þín." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Tölvupóstfang:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Endursstilla lykilorðið mitt" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Takk fyrir að verja tíma í vefsíðuna í dag." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Skráðu þig inn aftur" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Endurstilling á lykilorði tókst" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Við sendum nýtt lykilorð á tölvupóstfangið sem þú gafst upp. Það ætti að " +"berast fljótlega til þín." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Vinsamlegast skrifaðu gamla lykilorðið þitt til öryggis. Sláðu svo nýja " +"lykilorðið tvisvar inn svo að hægt sé að ganga úr skugga um að þú hafir ekki " +"gert innsláttarvillu." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Gamla lykilorðið:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nýja lykilorðið:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Staðfesta lykilorð:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Breyta lykilorðinu mínu" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Þú fékkst þennan tölvupóst vegna þess að þú baðst um endurstillingu á " +"lykilorðinu þínu" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "fyrir notandareikning þinn á %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Nýja lykilorðið þitt er: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Þér er frjálst að breyta lykilorðinu með því að fara á þessa síðu:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Notandanafnið þitt ef þú skyldir hafa gleymt því:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Takk fyrir að nota vefinn okkar!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s hópurinn" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bókamerklar" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Skjölunarbókamerklar" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +#, fuzzy +msgid "" +"\n" +"

                                                                To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                \n" +msgstr "" +"\n" +"

                                                                Til að setja upp bókamerkil (Bookmarklet) þarftu að draga " +"tengilinn\n" +"í bókamerkjareinina þína eða hægrismella á tengilinn og bæta honum við " +"bókamerkin þín\n" +"Nú getur þú notað bókamerkilinn frá hvaða síðu sem er innan vefjarins. " +"Athugaðu að sumir\n" +"þessara bókamerkla krefjast þess að þú sért að skoða vefinn frá tölvu sem " +"er\n" +"skráð sem \"internal\" (hafðu samband við kerfisstjórann ef þú ert óviss " +"hvort tölvan þín er \"internal\").

                                                                \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Skjölun þessarar síðu" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Sendir þig af hvaða síðu sem er á skjölun þess framsetningarlags sem myndar " +"hana." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Sýna kenni hlutar" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "Sýnir efnistag og sérkenni síða sem gefa tiltekna mynd af stökum hlut." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Breyta þessum hlut (í þessum glugga)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Stekkur á stjórnunarsíðuna fyrir þær síður sem gefa tiltekna mynd af stökum " +"hlut." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Breyta þessum hlut (nýr gluggi)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Eins og að ofan en opnar stjórnunarsíðuna í nýjum glugga." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Dagsetning:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Tími:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Eins og er:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Breyta:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "vísun frá" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "Þetta þarf að vera full slóð án lénsins. Dæmi: '/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "vísa á" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Þetta getur verið full slóð (eins og hér að ofan) eða veffang með 'http://' " +"fremst." + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "vísun" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "vísanir" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Dæmi: '/about/contact/'. Passaðu að hafa skástrik fremst og aftast." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titill" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "innihald" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "virkja athugasemdir" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nafn sniðmáts" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Dæmi: 'flatpages/contact_page'. Ef ekkert er gefið upp mun kerfið nota " +"'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "skráning nauðsynleg" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Ef þetta er valið geta eingöngu innskráðir notendur séð síðuna." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "flatskrá" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "flatskrár" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "nafn" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "vinnuheiti" + +#: contrib/auth/models.py:17 +#, fuzzy +msgid "permission" +msgstr "Réttindi" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +#, fuzzy +msgid "permissions" +msgstr "Réttindi" + +#: contrib/auth/models.py:29 +#, fuzzy +msgid "group" +msgstr "Hópur" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +#, fuzzy +msgid "groups" +msgstr "Hópar" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "notandanafn" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "skírnarnafn" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "eftirnafn" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "tölvupóstfang" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "lykilorð" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Notaðu '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "staða starfsmanns" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "" +"Segir til um hvort notandinn getur skráð sig inn á þetta stjórnunarsvæði." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "virkur" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "staða ofurnotanda" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "síðasta innskráning" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "skráning dags." + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Auk réttindanna sem notandanum var gefið sérstaklega fær hann öll þau " +"réttindi sem hópurinn hans hefur." + +#: contrib/auth/models.py:67 +#, fuzzy +msgid "user permissions" +msgstr "Réttindi" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "Notandi" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "Notendur" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Persónuupplýsingar" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Réttindi" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Mikilvægar dagsetningar" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Hópar" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "Skeyti" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Vafri þinn virðist ekki vera stilltur til að leyfa dúsur (cookies). Dúsur " +"eru nauðsynlegar fyrir innskráningu." + +#: contrib/contenttypes/models.py:25 +#, fuzzy +msgid "python model class name" +msgstr "nafn python-einingar" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "efnistag" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "efnistög" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "setulykill" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "setugögn" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "fyrningardagsetning" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "seta" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "setur" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "lén" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "birtingarnafn" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "vefur" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "vefir" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "j. N Y" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "j. N Y, H:i" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "mánudagur" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "þriðjudagur" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "miðvikudagur" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "fimmtudagur" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "föstudagur" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "laugardagur" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "sunnudagur" + +#: utils/dates.py:14 +msgid "January" +msgstr "janúar" + +#: utils/dates.py:14 +msgid "February" +msgstr "febrúar" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "mars" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "apríl" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "maí" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "júní" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "júlí" + +#: utils/dates.py:15 +msgid "August" +msgstr "ágúst" + +#: utils/dates.py:15 +msgid "September" +msgstr "september" + +#: utils/dates.py:15 +msgid "October" +msgstr "október" + +#: utils/dates.py:15 +msgid "November" +msgstr "nóvember" + +#: utils/dates.py:16 +msgid "December" +msgstr "desember" + +#: utils/dates.py:19 +#, fuzzy +msgid "jan" +msgstr "og" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +#, fuzzy +msgid "may" +msgstr "dagur" + +#: utils/dates.py:19 +msgid "jun" +msgstr "" + +#: utils/dates.py:20 +msgid "jul" +msgstr "" + +#: utils/dates.py:20 +msgid "aug" +msgstr "" + +#: utils/dates.py:20 +msgid "sep" +msgstr "" + +#: utils/dates.py:20 +msgid "oct" +msgstr "" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "ág." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "nóv." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "des." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "ár" +msgstr[1] "ár" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mánuður" +msgstr[1] "mánuðir" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "" +msgstr[1] "" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dagur" +msgstr[1] "dagar" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "klukkutími" +msgstr[1] "klukkutímar" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "mínúta" +msgstr[1] "mínútur" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengalska" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Tékkneska" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Velska" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Danska" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Þýska" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Enska" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Spænska" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Franska" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galíska" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Íslenska" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Ítalska" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japanska" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Hollenska" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norska" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brasilíska" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Rúmenska" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Rússneska" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Slóvaska" + +#: conf/global_settings.py:58 +#, fuzzy +msgid "Slovenian" +msgstr "Slóvaska" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Serbneska" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Sænska" + +#: conf/global_settings.py:61 +#, fuzzy +msgid "Ukrainian" +msgstr "Brasilíska" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Einfölduð Kínverska " + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Hefðbundin Kínverska" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Þetta gildi má einungis innihalda stafi, tölur og undirstrik." + +#: core/validators.py:64 +#, fuzzy +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Þetta gildi má einungis innihalda stafi, tölur, undirstrik og skástrik." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Hástafir eru ekki leyfðir hér." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Lágstafir eru ekki leyfðir hér." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Skrifaðu einungis tölur aðskildar með kommum." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Sláðu inn gild tölvupóstföng aðskilin með kommum." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Vinsamlegast sláðu inn gilda IP tölu." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Tóm gildi eru ekki leyfð hér." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Aðeins tölustafir eru leyfðir hér." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Þetta gildi verður að vera samsett úr fleiru en tölustöfum." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Sláðu inn heila tölu." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Einungis bókstafir eru leyfðir hér." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Sláðu inn gilda dagsetningu á YYYY-MM-DD sniði." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Sláðu inn gildan tíma á HH:MM sniði." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Sláðu inn gilda dagsetningu/tíma á YYYY-MM-DD HH:MM sniði." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Sláðu inn gilt tölvupóstfang." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Halaðu upp gildri myndskrá. Skráin sem þú halaðir upp var annað hvort gölluð " +"eða ekki mynd." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Veffangið %s bendir ekki á fullgilda mynd." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Símanúmer verða að vera á XXX-XXX-XXXX forminu. „%s“ er ógilt." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Veffangið %s vísar ekki á gilt QuickTime myndskeið." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Gilds veffangs er krafist." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Gilt HTML er nauðsynlegt. Tilteknar villur:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Illa formað XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ógilt veffang: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Veffangið %s er brotinn hlekkur." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Skrifaðu gilda styttingu á bandarísku fylkisnafni." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Passaðu orðbragðið! Orðið %s er ekki leyft hér." +msgstr[1] "Passaðu orðbragðið! Orðin %s eru ekki leyfð hér." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Þessi reitur verður að passa við „%s“ reitinn." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Vinsamlegast fylltu út einn reit að minnsta kosti." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Vinsamlegast fylltu út í báða reitina eða skildu þá eftir tóma." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Þessi reitur þarf að vera útfylltur ef %(field)s er %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Þessi reitur verður að vera útfylltur ef %(field)s er ekki %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Endurtekin gildi eru ekki leyfð." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Þessi reitur verður að vera veldi af %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Vinsamlegast settu inn gilda tugatölu." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Vinsamlegast skrifaðu gilda tugatölu með hámark %s tölustaf." +msgstr[1] "Vinsamlegast skrifaðu gilda tugatölu með hámark %s tölustafi." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Vinsamlegast skrifaðu gilda tugatölu með hámark %s tugbrotastaf." +msgstr[1] "Vinsamlegast skrifaðu gilda tugatölu með hámark %s tugbrotastafi." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Gakktu úr skugga um að upphöluð skrá sé að minnsta kosti %s bæti að stærð." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Gakktu úr skugga um að upphlaðin skrá sé í mesta lagi %s bæti að stærð." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Sniðið á þessum reit í rangt." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Þessi reitur er ótækur." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Gat engu náð úr %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "Veffangið %(url)s skilaði ótæka efnistagshausnum '%(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Vinsamlegast lokaðu opna %(tag)s taginu sem byrjar á línu %(line)s. (Línan " +"hefst á \"%(start)s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Texti sem hefst á línu %(line)s er ekki leyfður í því samhengi. (Line starts " +"with \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" á línu %(line)s er ótækt eigindi (línan hefst á \"%(start)s\")." + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" á línu %(line)s er ótækt tag. (Línan hefst á \"%(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Tag á línu %(line)s vantar eitt eða fleiri nauðsynleg eigindi. (Línan hefst " +"á \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"\"%(attr)s\" eigindið á línu %(line)s hefur ótækt gildi (línan hefst á \"%" +"(start)s\")." + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" +"%(object)s með þetta %(type)s er nú þegar til fyrir uppgefið %(field)s." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s með þetta %(fieldname)s er nú þegar til." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Þennan reit þarf að útfylla." + +#: db/models/fields/__init__.py:337 +#, fuzzy +msgid "This value must be an integer." +msgstr "Þessi reitur verður að vera veldi af %s." + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "Þessi reitur verður að vera veldi af %s." + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "Þessi reitur er ótækur." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Sláðu inn gilt tölvupóstfang." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Vinsamlegast sláðu inn fullgilt %s." + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr "Notaðu kommur til að aðskilja kenni." + +#: db/models/fields/related.py:581 +#, fuzzy +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Haltu inni „Control“, eða „Command“ á Mac til þess að velja fleira en eitt." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Vinsamlegast sláðu inn gild %(self)s kenni. Gildið %(value)r er ógilt." +msgstr[1] "" +"Vinsamlegast sláðu inn gild %(self)s kenni. Gildin %(value)r eru ógild." + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Gangtu úr skugga um að textinn þin sé styttri en %s tákn." +msgstr[1] "Gangtu úr skugga um að textinn þin sé styttri en %s tákn." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Línuskil eru ekki leyfð hér." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Veldu gildan valmöguleika; „%(data)s“ er ekki í %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "Innsend skrá er tóm." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Sláðu inn heila tölu á bilinu -32.768 til 32.767." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Sláðu inn jákvæða tölu." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Sláðu inn heila tölu á bilinu 0 til 32.767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "já,nei,kannski" + +#~ msgid "Comment" +#~ msgstr "Athugasemd" + +#~ msgid "Comments" +#~ msgstr "Athugasemdir" + +#~ msgid "" +#~ "This comment was posted by a user who has posted fewer than %(count)s " +#~ "comment:\n" +#~ "\n" +#~ "%(text)sThis comment was posted by a user who has posted fewer than %" +#~ "(count)s comments:\n" +#~ "\n" +#~ "%(text)s" +#~ msgstr "" +#~ "Þessi athugasemd var send inn af notanda sem hefur skrifað færri en %" +#~ "(count)s athugasemd:\n" +#~ "\n" +#~ "%(text)sÞessi athugasemd var send inn af notanda sem hefur skrifað færri " +#~ "en %(count)s athugasemdir:\n" +#~ "\n" +#~ "%(text)s" + +#~ msgid "String (up to 50)" +#~ msgstr "Strengur (allt að 50)" + +#~ msgid "label" +#~ msgstr "merki" + +#~ msgid "package" +#~ msgstr "pakki" + +#~ msgid "packages" +#~ msgstr "pakkar" + +#, fuzzy +#~ msgid "count" +#~ msgstr "innihald" diff --git a/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/is/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..55a333b5a5f525b09d422db9e7ff2ee09f7a4eb9 GIT binary patch literal 1609 zcwTLjPi!MK6voZADQp&4D1RiRMY4oeutF1(!Y(BgL^f@-U7Bvygf1L7xHIl#5|15u z>_8+A>>X}!;eb>mdO?*cE$X2NF6M%S#C?&taN`2pkl?`QnaP%waAg0!vEO@swqJf( zod1-dJ&Aq+{YUht(LcD4+>OuI$HJB5 zIs1Kl4v(WhR{Hwf&XxL6KU|@#mexosib!k162W!_rx!EQW`fIzX$To|LD~bQGENya zNy_hjiF_gBGItT2>0(Z(a73;%X_=nV4&T+e=rSD+Xltw!0X?PHt>hTQl(uYaGediP zY(fc_a(fA6T7>j!ry!%vcRU`l=XHr8Rbc zhi8W83tL(Zv1hOz1v^}4iIPpaI%G0d!;H3fdev<{R@wyJEKZ`}s*SUtubQ;Fb)z4& zHD?AM!74MnNtf#N%Ryr)sJ}uRjppUX^Yuo(4#QxZPm=5&yDNb=ebL*!v>epYsv9gb zL0>bO31*Z=7A(d!Y`J|QXe&+eu-cC4O5~@b&eA4b$IIi& zlJNg*yX)Px(}Ol@^=ey5!zH$!j**+;@0%CKf+g}*ya3Y~H?LW<7c8IJ`4soK4%TF- zqD00`y1bj1>f6DV;!$FPSvvp8a`sJOM, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Djangojs CVS\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-05-13 11:48-0000\n" +"Last-Translator: Dagur Páll Ammendrup \n" +"Language-Team: Icelandic \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Icelandic\n" +"X-Poedit-Country: ICELAND\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Fáanleg %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Velja öll" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Bæta við" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Fjarlægja" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Valin %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Veldu úr valmöguleikunum og smelltu" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Hreinsa öll" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "January February March April May June July August September October November December" +msgstr "janúar febrúar mars apríl maí júní júlí ágúst september október nóvember desember" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "sunnudagur mánudagur þriðjudagur miðvikudagur fimmtudagur föstudagur laugardagur" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "S M Þ M F F L" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Núna" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Klukka" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Veldu tíma" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Miðnætti" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 f.h." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Hádegi" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Hætta við" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Í dag" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Dagatal" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Í gær" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Á morgun" + diff --git a/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..821828383effdf72124e76b8d8cb674e7437612b GIT binary patch literal 40300 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 IYrgaU0sT}xg#Z8m literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000..66a4e09 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,2297 @@ +# translation of django.po to Italiano +# Italian translation of Django. +# Copyright (C) 2006 the Lawrence Journal-World +# This file is distributed under the same license as the Django package. +# +# Carlo C8E Miron , 2006. +# Nicola 'tekNico' Larosa , 2007. +# Nicola Larosa , 2007. +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-26 20:44+0100\n" +"PO-Revision-Date: 2007-03-14 19:29+0100\n" +"Last-Translator: Nicola Larosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language-Team: Italiano\n" + +#: db/models/manipulators.py:307 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s·con questo·%(type)s·esiste già per questo·%(field)s." + +#: db/models/manipulators.py:308 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "e" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Inserire un %s valido." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Separare ID multipli con virgole." + +#: db/models/fields/related.py:644 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Tenere premuto \"Control\", o \"Command\" su Mac, per selezionarne più di uno." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Inserire un ID validi per %(self)s. Il valore %(value)r non è valido." +msgstr[1] "Inserire un ID validi per %(self)s. I valori %(value)r non sono validi." + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s·con questo·%(fieldname)s·esiste già." + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:609 db/models/fields/__init__.py:620 +#: oldforms/__init__.py:352 newforms/fields.py:78 newforms/fields.py:374 +#: newforms/fields.py:450 newforms/fields.py:461 newforms/models.py:177 +msgid "This field is required." +msgstr "Questo campo è obbligatorio." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Questo valore deve essere un intero." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Questo valore deve essere True o False." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Questo campo non può essere nullo." + +#: db/models/fields/__init__.py:456 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Inserire una data valida in formato AAAA-MM-GG." + +#: db/models/fields/__init__.py:525 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Inserire una data/ora valida in formato AAAA-MM-GG OO:MM." + +#: db/models/fields/__init__.py:629 +msgid "Enter a valid filename." +msgstr "Inserire un nome file valido." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Arabo" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengali" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "Catalano" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "Ceco" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "Gallese" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "Danese" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "Tedesco" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "Greco" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "Inglese" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "Spagnolo" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "Spagnolo argentino" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "Finlandese" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "Francese" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "Galiziano" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "Ungherese" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "Ebraico" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "Islandese" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "Italiano" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "Giapponese" + +#: conf/global_settings.py:58 +msgid "Kannada" +msgstr "Kannada" + +#: conf/global_settings.py:59 +msgid "Latvian" +msgstr "Lettone" + +#: conf/global_settings.py:60 +msgid "Macedonian" +msgstr "Macedone" + +#: conf/global_settings.py:61 +msgid "Dutch" +msgstr "Olandese" + +#: conf/global_settings.py:62 +msgid "Norwegian" +msgstr "Norvegese" + +#: conf/global_settings.py:63 +msgid "Polish" +msgstr "Polacco" + +#: conf/global_settings.py:64 +msgid "Brazilian" +msgstr "Brasiliano" + +#: conf/global_settings.py:65 +msgid "Romanian" +msgstr "Rumeno" + +#: conf/global_settings.py:66 +msgid "Russian" +msgstr "Russo" + +#: conf/global_settings.py:67 +msgid "Slovak" +msgstr "Slovacco" + +#: conf/global_settings.py:68 +msgid "Slovenian" +msgstr "Sloveno" + +#: conf/global_settings.py:69 +msgid "Serbian" +msgstr "Serbo" + +#: conf/global_settings.py:70 +msgid "Swedish" +msgstr "Svedese" + +#: conf/global_settings.py:71 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:72 +msgid "Turkish" +msgstr "Turco" + +#: conf/global_settings.py:73 +msgid "Ukrainian" +msgstr "Ucraino" + +#: conf/global_settings.py:74 +msgid "Simplified Chinese" +msgstr "Cinese semplificato" + +#: conf/global_settings.py:75 +msgid "Traditional Chinese" +msgstr "Cinese tradizionale" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Questo valore può contenere solo lettere, cifre e sottolineature." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Questo valore può contenere solo lettere, cifre, sottolineature, trattini e " +"barre diagonali." + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Questo valore può contenere solo lettere, cifre, sottolineature e trattini." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Non sono ammesse lettere maiuscole." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Non sono ammesse lettere minuscole." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Inserire solo cifre separate da virgole." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Inserire indirizzi e-mail validi separati da virgole." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Inserire un indirizzo IP valido." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "È necessario inserire un valore." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Sono ammessi soltanto caratteri numerici." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Questo valore non può essere composto solo da cifre." + +#: core/validators.py:120 newforms/fields.py:126 +msgid "Enter a whole number." +msgstr "Inserire un numero intero." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Sono ammessi solo caratteri alfabetici." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "L'anno deve essere 1900 o successivo." + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Data non valida: %s." + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Inserire un orario valido in formato OO:MM." + +#: core/validators.py:161 newforms/fields.py:269 +msgid "Enter a valid e-mail address." +msgstr "Inserire un indirizzo e-mail valido." + +#: core/validators.py:173 core/validators.py:444 oldforms/__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Non è stato inviato alcun file. Verificare il tipo di codifica della form." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Caricare un'immagine valida. Il file caricato non è un'immagine o è corrotto." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "La URL %s non punta ad un'immagine valida." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "I numeri di telefono devono essere in formato XXX-XXX-XXXX. \"%s\" non è valido." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "La URL %s non punta ad un video QuickTime valido." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Inserire una URL valida." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"È richiesto HTML valido. Gli errori sono i seguenti:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML malformato: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL non valida: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "La URL %s è un link non funzionante." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Inserire un valido nome di stato USA abbreviato." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Moderare i termini: la parola %s non è ammessa." +msgstr[1] "Moderare i termini: le parole %s non sono ammesse." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Questo campo deve corrispondere al campo '%s'." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Inserire qualcosa in almeno un campo." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Inserire entrambi i campi o lasciarli entrambi vuoti." + +#: core/validators.py:319 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Questo campo è obbligatorio se %(field)s è %(value)s" + +#: core/validators.py:332 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Questo campo è obbligatorio se %(field)s non è %(value)s" + +#: core/validators.py:351 +msgid "Duplicate values are not allowed." +msgstr "Non sono ammessi valori duplicati." + +#: core/validators.py:366 +#, python-format +msgid "This value must be between %s and %s." +msgstr "Questo valore deve essere compreso tra %s e %s." + +#: core/validators.py:368 +#, python-format +msgid "This value must be at least %s." +msgstr "Questo valore deve essere almeno pari a %s." + +#: core/validators.py:370 +#, python-format +msgid "This value must be no more than %s." +msgstr "Questo valore non deve essere maggiore di %s." + +#: core/validators.py:406 +#, python-format +msgid "This value must be a power of %s." +msgstr "Questo valore deve essere una potenza di %s." + +#: core/validators.py:417 +msgid "Please enter a valid decimal number." +msgstr "Inserire un numero decimale valido." + +#: core/validators.py:421 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Inserire un numero decimale con non più di %s cifra in totale." +msgstr[1] "Inserire un numero decimale con non più di %s cifre in totale." + +#: core/validators.py:424 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Inserire un numero decimale la cui parte intera sia composta da non più di %s cifra." +msgstr[1] "Inserire un numero decimale la cui parte intera sia composta da non più di %s cifre." + +#: core/validators.py:427 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Inserire un decimale con non più di %s cifra decimale." +msgstr[1] "Inserire un decimale con non più di %s cifre decimali." + +#: core/validators.py:437 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Verificare che il file caricato sia grande almeno %s byte." + +#: core/validators.py:438 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Verificare che il file caricato non sia più grande di %s byte." + +#: core/validators.py:455 +msgid "The format for this field is wrong." +msgstr "Il formato di questo campo non è valido." + +#: core/validators.py:470 +msgid "This field is invalid." +msgstr "Questo campo non è valido." + +#: core/validators.py:506 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Impossibile recuperare alcunché da %s." + +#: core/validators.py:509 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "La URL %(url)s ha restituito un header Content-Type non valido: '%(contenttype)s'." + +#: core/validators.py:542 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "Chiudere il tag %(tag)s a linea %(line)s. (La linea inizia con \"%(start)s\".)" + +#: core/validators.py:546 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Il testo che comincia a linea %(line)s non e' ammesso in questo contesto. " +"(La linea comincia con \"%(start)s\".)" + +#: core/validators.py:551 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "\"%(attr)s\" a linea %(line)s non è un attributo valido. (La linea comincia con \"%(start)s\".)" + +#: core/validators.py:556 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" a linea %(line)s non è un tag valido. (La linea comincia con \"%" +"(start)s\".)" + +#: core/validators.py:560 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Un tag a linea %(line)s manca di uno o più attributi richiesti. (La linea " +"comincia con \"%(start)s\".)" + +#: core/validators.py:565 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"L'attributo \"%(attr)s\" a linea %(line)s ha un valore non valido. (La " +"linea comincia con \"%(start)s\".)" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "I due campi password non corrispondono." + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "Un utente con questo nome·è già presente." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Il browser web sembra non avere i cookie abilitati. I cookie sono necessari per poter accedere." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Inserire nome utente e password corretti. Entrambi i campi sono case " +"sensitive." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "Questo account non è attivo." + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "Questo indirizzo email non è associato ad alcun account utente. Sei sicuro di esserti registrato?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "I due campi 'nuova password' non corrispondono." + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "La vecchia password non è stata inserita correttamente: va inserita di nuovo." + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nome" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nome in codice" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permesso" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permessi" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "gruppo" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "gruppi" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "nome utente" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "Obbligatorio. 30 caratteri o meno. Solo caratteri alfanumerici (lettere, cifre e sottolineature)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "nome" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "cognome" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "indirizzo e-mail" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "password" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "Usare '[algo]$[salt]$[hexdigest]' oppure la maschera di cambio password." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "privilegi di staff" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Indica se l'utente può accedere a questo sito di amministrazione." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "attivo" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "Indica se l'utente può accedere all'amministrazione di Django. Deselezionare qui, piuttosto che cancellare gli account." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "privilegi di superutente" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "Indica che l'utente ha tutti i privilegi, senza che siano stati assegnati esplicitamente." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "ultimo accesso" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "iscritto in data" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"In aggiunta ai privilegi assegnati manualmente, l'utente riceverà anche tutti " +"i privilegi assegnati ad ogni gruppo cui appartiene." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "privilegi utente" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "utente" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "utenti" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Informazioni personali" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Privilegi" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Date importanti" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Gruppi" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "messaggio" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Accesso annullato" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "data azione" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "ID oggetto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "rappresentazione oggetto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "flag azione" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "messaggio di modifica" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "voce di log" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "voci di log" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                By %s:

                                                                \n" +"
                                                                  \n" +msgstr "" +"

                                                                  Da %s:

                                                                  \n" +"
                                                                    \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Tutti" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Qualsiasi data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Oggi" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Ultimi 7 giorni" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Questo mese" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Quest'anno" + +#: contrib/admin/filterspecs.py:143 oldforms/__init__.py:572 +#: newforms/widgets.py:170 +msgid "Yes" +msgstr "Sì" + +#: contrib/admin/filterspecs.py:143 oldforms/__init__.py:572 +#: newforms/widgets.py:170 +msgid "No" +msgstr "No" + +#: contrib/admin/filterspecs.py:150 oldforms/__init__.py:572 +#: newforms/widgets.py:170 +msgid "Unknown" +msgstr "Sconosciuto" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Accedi" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"La sessione è scaduta: occorre accedere nuovamente. I dati inseriti sono stati comunque" +"salvati." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "Il browser non sembra configurato per accettare i cookie. Una volta abilitati, ricaricare la pagina e riprovare." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "I nomi utente non possono contenere il carattere '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Il nome utente non è costituito dall'indirizzo e-mail. Provare con '%s'." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Amministrazione sito" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" è stato aggiunto correttamente." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "È possibile modificarlo nuovamente qui sotto." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "È possibile aggiungere un altro %s qui sotto." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Aggiungere %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Aggiunto %s" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Modificato %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Cancellato %s" + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Nessun campo modificato." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" è stato modificato correttamente." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" è stato aggiunto correttamente. È possibile modificarlo nuovamente qui sotto." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Modificare %s" + +#: contrib/admin/views/main.py:476 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Uno o più %(fieldname)s in %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:481 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Uno o più %(fieldname)s in %(name)s:" + +#: contrib/admin/views/main.py:514 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" è stato cancellato correttamente." + +#: contrib/admin/views/main.py:517 +msgid "Are you sure?" +msgstr "Sei sicuro?" + +#: contrib/admin/views/main.py:539 +#, python-format +msgid "Change history: %s" +msgstr "Tracciato delle modifiche: %s" + +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s" +msgstr "Seleziona %s" + +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s to change" +msgstr "Seleziona %s per modificare" + +#: contrib/admin/views/main.py:768 +msgid "Database error" +msgstr "Errore nel database" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "tag:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filtro:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "view:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "Appl. %r non trovata" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Modello %r non trovato nell'appl. %r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "l'oggetto `%s.%s` collegato" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "modello:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "oggetti `%s.%s` collegati" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "tutti %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "numero di %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Campi sugli oggetti %s" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Intero" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Booleano (True o False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Stringa (fino a %(maxlength)s caratteri)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Interi separati da virgola" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Data (senza orario)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Data (con orario)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Indirizzo e-mail" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Percorso di file" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Numero decimale" + +#: contrib/admin/views/doc.py:304 contrib/comments/models.py:85 +msgid "IP address" +msgstr "indirizzo IP" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Booleano (True, False o None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Collegamento a modello padre" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Numero di telefono" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Testo" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Orario" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Stato USA (due lettere maiuscole)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "Testo XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s non sembra essere un oggetto urlpattern" + +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "Aggiungi utente" + +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." +msgstr "La password è stata cambiata correttamente." + +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "Cambia la password: %s" + +#: contrib/admin/templatetags/admin_list.py:247 +msgid "All dates" +msgstr "Tutte le date" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Mostra tutto" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Documentazione" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Cambia la password" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/comments/templates/comments/form.html:6 +msgid "Log out" +msgstr "Esci" + +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Home" +msgstr "Pagina iniziale" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Cancella" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"La cancellazione di %(object_name)s '%(escaped_object)s' causerebbe la cancellazione " +"di oggetti collegati, ma questo account non ha i permessi per cancellare gli oggetti dei seguenti tipi:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Sei sicuro di voler rimuovere %(object_name)s \"%(escaped_object)s\"? Tutti i seguenti " +"oggetti collegati saranno cancellati:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Sì, sono sicuro" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Pagina non trovata" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Spiacenti, ma la pagina richiesta non è stata trovata." + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Aggiungi" + +#: contrib/admin/templates/admin/change_form.html:21 +#: contrib/admin/templates/admin/object_history.html:5 +msgid "History" +msgstr "Storia" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Vedi sul sito" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Correggere l'errore qui sotto." +msgstr[1] "Correggere gli errori qui sotto." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Ordinamento" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Ordine:" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Per %(filter_title)s " + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Salva come nuovo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Salva e aggiungi un altro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Salva e continua le modifiche" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Salva" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Aggiungi %(name)s" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelli disponibili nell'applicazione %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modifica" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Non hai i privilegi per modificare alcunché." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Azioni Recenti" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Azioni Proprie" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nessuno disponibile" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Amministrazione sito Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Amministrazione Django" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/orario" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Utente" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Azione" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j F Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "Questo oggetto non ha cambiamenti registrati. Probabilmente non è stato creato con questo sito di amministrazione." + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Errore del server" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Errore del server (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Errore del server (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "Si è verificato un errore. È stato riportato agli amministratori del sito via e-mail e verrà corretto a breve. Grazie per la tua pazienza." + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "Ci sono problemi nell'installazione del database. Assicurarsi che le tabelle appropriate del database siano state create, e che il database sia leggibile dall'utente appropriato." + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Vai" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 risultato" +msgstr[1] "%(counter)s risultati" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s totali" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtro" + +#: contrib/admin/templates/admin/login.html:17 +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +msgid "Username:" +msgstr "Nome utente:" + +#: contrib/admin/templates/admin/login.html:20 +#: contrib/comments/templates/comments/form.html:8 +msgid "Password:" +msgstr "Password:" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Hai dimenticato la password?" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Benvenuto," + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "Inserire innanzitutto nome utente e password. Si potrà quindi modificare le altre impostazioni dell'utente." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Nome utente" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +msgid "Password" +msgstr "Password" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +msgid "Password (again)" +msgstr "Password (di nuovo)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "Inserire la stessa password inserita sopra, come verifica." + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "Inserire una nuova password per l'utente %(username)s." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklet" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Bookmarklet alla documentazione" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                    To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                    \n" +msgstr "" +"\n" +"

                                                                    Per installare i bookmarklet, trascinare il link sulla barra \n" +"dei bookmark, o cliccare il link con il tasto destro e aggiungerlo ai bookmark.\n" +"Sarà quindi possibile selezionare un bookmarklet in qualsiasi pagina del sito.\n" +"Si noti che alcuni di questi bookmarklet richiedono l'accesso al sito tramite un\n" +"computer designato come \"interno\" (chiedere al proprio amministratore di \n" +"sistema se non si è sicuri che il proprio computer sia \"interno\").

                                                                    \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentazione per questa pagina" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Porta da qualsiasi pagina alla documentazione della view che genera " +"quella pagina." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostra l'ID dell'oggetto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "Mostra il content-type e l'ID univoco di pagine che rappresentano un singolo oggetto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Modifica quest'oggetto (nella finestra corrente)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Porta alla pagina amministrativa di pagine che rappresentano un oggetto singolo." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Modifica quest'oggetto (in una nuova finestra)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Come sopra, ma apre la pagina di amministrazione in una nuova finestra." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Orario:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Attualmente:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modifica:" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Reimposta la password" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "Dimenticata la password? Inserire il proprio indirizzo e-mail qui sotto: la password sarà reimpostata, e la nuova ti verrà inviata per e-mail." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Indirizzo e-mail:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Reimposta la mia password" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Hai ricevuto questa e-mail perché hai chiesto di reimpostare la password" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "per il tuo account utente su %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "La tua nuova password è: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Puoi liberamente cambiare la tua password tramite questa pagina:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Il tuo nome utente, in caso l'abbia dimenticato:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Grazie per aver usato il nostro sito!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Il team di %(site_name)s" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Grazie per aver speso il tuo tempo prezioso su questo sito oggi." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Accedi di nuovo" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Password reimpostata correttamente" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "La nuova password è stata inviata all'indirizzo e-mail inserito. Arriverà a breve." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Cambio password" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "Inserire l'attuale password, per ragioni di sicurezza, e poi la nuova password due volte, per verificare di averla scritta correttamente." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Password attuale:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nuova password:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confermare la password:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Modifica la mia password" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Cambio di password avvenuto correttamente" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "La password è stata cambiata." + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nome di dominio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nome visualizzato" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "sito" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "siti" + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Esempio: '/about/contact/'. Assicurarsi di inserire le barre diagonali iniziali e finali." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titolo" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "contenuto" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "abilita commenti" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nome modello" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "Esempio: 'flatpages/contact_page.html'. Se non specificato, il sistema userà 'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "registrazione obbligatoria" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Se selezionato, solo gli utenti che hanno effettuato l'accesso potranno vedere la pagina." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "pagina statica" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "pagine statiche" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirigi da" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "Deve essere un percorso assoluto, senza nome di dominio. Esempio: '/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirigi verso" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "Può essere un percorso assoluto (come sopra) o una URL completa che inizia con 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redirezione" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redirezioni" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID dell'oggetto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "intestazione" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "commento" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "valutazione #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "valutazione #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "valutazione #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "valutazione #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "valutazione #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "valutazione #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "valutazione #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "valutazione #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "è una valutazione valida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/orario di inserimento" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "è pubblico" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "è rimosso" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "Spuntare la casella se il commento è inappropriato. Verrà sostituito dal messaggio \"Questo commento è stato rimosso\"." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "commenti" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Oggetto con contenuto" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Inserito da %(user)s il %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nome della persona" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "indirizzo IP" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "approvato dallo staff" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "commento libero" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "commenti liberi" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "punteggio" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data punteggio" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "livello karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "livelli karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "valutazione: %(score)d da %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"A questo commento è stato apposto un flag da %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "data flag" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "flag utente" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "flag utente" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Flag da %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data cancellazione" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "cancellazione da moderatore" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "cancellazioni da moderatore" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Cancellazione da moderatore %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Gli utenti anonimi non possono votare" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID commento non valido" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Impossibile votare per se stessi" + +#: contrib/comments/views/comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "Questa valutazione è obbligatoria perché hai inserito almeno un'altra valutazione." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Questo commento è stato inserito da un utente autore di meno di %(count)s commento:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Questo commento è stato inserito da un utente autore di meno di %(count)s commenti:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Questo commento è stato inserito da un utente non confermato:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Sono ammessi solo POST" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Uno o più campi richiesti non sono stati inseriti" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Qualcuno ha alterato il modulo di commento (violazione di sicurezza)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"Il modulo di commento ha un parametro 'target' non valido -- l'ID " +"dell'oggetto non e` valido" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Il modulo di commento non fornisce né 'anteprima' né 'invia'" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Hai dimenticato la password?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Valutazioni" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Obbligatorio" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Facoltativo" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Invia una foto" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Commento:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Anteprima commento" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Il suo nome:" + +#: contrib/localflavor/uk/forms.py:18 +msgid "Enter a postcode. A space is required between the two postcode parts." +msgstr "Inserire un codice postale. È obbligatorio uno spazio tra le due parti del codice postale." + +#: contrib/localflavor/usa/forms.py:17 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "Inserire un codice postale nel formato XXXXX o XXXXX-XXXX." + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "chiave di sessione" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "dati di sessione" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "data di scadenza" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sessione" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sessioni" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "nome della classe modello in Python" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "content type" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "content type" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Assicurarsi che il testo sia più corto di %s carattere." +msgstr[1] "Assicurarsi che il testo sia più corto di %s caratteri." + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "Non sono ammessi a capo manuali." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Selezionare un'opzione valida; '%(data)s' non presente in %(choices)s." + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "Il file inviato è vuoto." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Inserire un numero intero compreso tra -32.768 e 32.767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Inserire un numero positivo." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Inserire un numero intero compreso tra 0 e 32.767." + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s è stato creato correttamente." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s è stato aggiornato correttamente." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(verbose_name)s è stato cancellato." + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Lunedì" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Martedì" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Mercoledì" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Giovedì" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Venerdì" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sabato" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Domenica" + +#: utils/dates.py:14 +msgid "January" +msgstr "Gennaio" + +#: utils/dates.py:14 +msgid "February" +msgstr "Febbraio" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marzo" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Aprile" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maggio" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Giugno" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Luglio" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "Settembre" + +#: utils/dates.py:15 +msgid "October" +msgstr "Ottobre" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembre" + +#: utils/dates.py:16 +msgid "December" +msgstr "Dicembre" + +#: utils/dates.py:19 +msgid "jan" +msgstr "gen" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "apr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mag" + +#: utils/dates.py:19 +msgid "jun" +msgstr "giu" + +#: utils/dates.py:20 +msgid "jul" +msgstr "lug" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "ott" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dic" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Gen." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Ott." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dic." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "anno" +msgstr[1] "anni" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mese" +msgstr[1] "mesi" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "settimana" +msgstr[1] "settimane" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "giorno" +msgstr[1] "giorni" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "ora" +msgstr[1] "ore" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minuti" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j F Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j F Y, H:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "Y F" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "F j" + +#: template/defaultfilters.py:490 +msgid "yes,no,maybe" +msgstr "sì,no,forse" + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Assicurarsi che questo valore non contenga più di %d caratteri." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Assicurarsi che questo valore contenga almeno %d caratteri." + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Assicurarsi che questo valore sia minore o uguale a %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Assicurarsi che questo valore sia maggiore o uguale a %s." + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "Inserire una data valida." + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "Inserire un orario valido." + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "Inserire una coppia data/orario valida." + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "Inserire un valore valido." + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "Inserire una URL valida." + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "Questa URL non sembra funzionare." + +#: newforms/fields.py:360 newforms/models.py:164 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "Selezionare un'opzione valida. La scelta effettuata non compare tra quelle disponibili." + +#: newforms/fields.py:378 newforms/fields.py:454 newforms/models.py:181 +msgid "Enter a list of values." +msgstr "Inserire una lista di valori." + +#: newforms/fields.py:387 newforms/models.py:187 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Selezionare un'opzione valida;'%s non compare tra quelle disponibili." + diff --git a/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..c34009f14827be9f1c1765f39cb4e29c5a7f0544 GIT binary patch literal 1643 zcwTjr&u<(x6vqvl4P^_Z6be1`@Or4!2u->hwISLxsA$ zvOR%BoO|NJfg67U{{shZ`~jT!aYCGskPt}R_mvnA8}|y69*g%?#^Qn0m9(d{!DCws18LIbV@VrQOt9b+^;pNM@fVI_t%pb1#mQ&U+=Cq|`w!_AS@8Ks3vk4X$CI6ACQC=xR@MisljN1W*>jV6_?g$=6$ zhSFCSf*6_jEseGC^O&9ITCr!%5dtS$S#rSEDWm(89Z8*=BiGsIsj)s8xLjq)y()Lf z$n>eRw>wJKEK3jTWL-jir&ujMYbp~65`_B^Y&38L%Mtjz9b2>#F&!766 z(?Tk}jO&!vv0r&O+DMk3*@bXV*e3UNYBHtsJ}r%v@9Y(ol|`~)Z0Y($dRnjD%EB^5 zapi`lYb(^7Tkaeto2=Q!oIkiH$6O$nUT=P(gKo{RDy8>I8?l<_s?xa<>&i`yR%7+w z-c>Nsx+)4OhAHQTqDqsowW8VZmG||c@FpG&-Huy|id}08lf!&Rx)lAP1ai|-nb6N; z8wB+8nTh?(SRf@so2tsSq3sGGpv#Se_aXbx*OqA)t^;FVLs(a7ErUQ%Pcg_*RN_Q!*YM9e`gh o`}XUQx{m@|v_>s%DkK$zgYXDJ7|O8>SCNN{Hl@@`Y_V11A3C^<_W%F@ literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..4f01cd0 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/it/LC_MESSAGES/djangojs.po @@ -0,0 +1,123 @@ +# translation of djangojs.po to Italiano +# Italian translation for the django-admin JS files +# Copyright (C) 2006 the Lawrence Journal-World +# This file is distributed under the same license as the Django package. +# +# Carlo C8E Miron , 2006. +# Nicola 'tekNico' Larosa , 2007. +msgid "" +msgstr "" +"Project-Id-Version: djangojs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-26 20:46+0100\n" +"PO-Revision-Date: 2007-02-26 20:55+0100\n" +"Last-Translator: Nicola Larosa \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: KBabel 1.11.2\n" + +#: contrib/admin/media/js/calendar.js:24 +#: contrib/admin/media/js/dateparse.js:32 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Gennaio Febbraio Marzo Aprile Maggio Giugno Luglio Agosto Settembre Ottobre " +"Novembre Dicembre" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D L M M G V S" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Domenica Lunedì Martedì Mercoledì Giovedì Venerdì Sabato" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Disponibile %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Scegli tutto" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Aggiungi" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Rimuovi" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Scelto %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Seleziona le tue scelte e clicca " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Cancella tutto" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "Adesso" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "Orologio" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "Scegli un orario" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "Mezzanotte" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "6 del mattino" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "Mezzogiorno" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "Annulla" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "Oggi" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "Calendario" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "Ieri" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "Domani" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Mostra" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Nascondi" + diff --git a/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..97b8f4145bcb7fc15caa8b79e9b3e6861ad7cf01 GIT binary patch literal 43571 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 vWU>5RaXCm, 2005,2006,2007. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 23:43+0900\n" +"PO-Revision-Date: 2006-05-18 00:28+0900\n" +"Last-Translator: makoto tsuyuki \n" +"Language-Team: Japanese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "アラビア語" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "ベンガル語" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "カタロニア語" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "チェコ語" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "ウェールズ語" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "デンマーク語" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "ドイツ語" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "ギリシャ語" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "英語" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "スペイン語" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "アルゼンチンスペイン語" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "フィンランド語" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "フランス語" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "ガリシア語" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "ハンガリー語" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "ヘブライ語" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "アイスランド語" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "イタリア語" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "日本語" + +#: conf/global_settings.py:58 +msgid "Latvian" +msgstr "ラトビア語" + +#: conf/global_settings.py:59 +msgid "Macedonian" +msgstr "マケドニア語" + +#: conf/global_settings.py:60 +msgid "Dutch" +msgstr "オランダ語" + +#: conf/global_settings.py:61 +msgid "Norwegian" +msgstr "ノルウェー語" + +#: conf/global_settings.py:62 +msgid "Polish" +msgstr "ポーランド語" + +#: conf/global_settings.py:63 +msgid "Brazilian" +msgstr "ブラジル語" + +#: conf/global_settings.py:64 +msgid "Romanian" +msgstr "ルーマニア語" + +#: conf/global_settings.py:65 +msgid "Russian" +msgstr "ロシア語" + +#: conf/global_settings.py:66 +msgid "Slovak" +msgstr "スロバキア語" + +#: conf/global_settings.py:67 +msgid "Slovenian" +msgstr "スロヴェニア語" + +#: conf/global_settings.py:68 +msgid "Serbian" +msgstr "セルビア語" + +#: conf/global_settings.py:69 +msgid "Swedish" +msgstr "スウェーデン語" + +#: conf/global_settings.py:70 +msgid "Tamil" +msgstr "タミル語" + +#: conf/global_settings.py:71 +msgid "Turkish" +msgstr "トルコ語" + +#: conf/global_settings.py:72 +msgid "Ukrainian" +msgstr "ウクライナ語" + +#: conf/global_settings.py:73 +msgid "Simplified Chinese" +msgstr "簡体字中国語" + +#: conf/global_settings.py:74 +msgid "Traditional Chinese" +msgstr "繁体字中国語" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                    By %s:

                                                                    \n" +"
                                                                      \n" +msgstr "" +"

                                                                      %s で絞り込む

                                                                      \n" +"
                                                                        \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "全て" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "いつでも" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "今日" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "過去 7 日間" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "今月" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "今年" + +#: contrib/admin/filterspecs.py:143 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "Yes" +msgstr "はい" + +#: contrib/admin/filterspecs.py:143 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "No" +msgstr "いいえ" + +#: contrib/admin/filterspecs.py:150 newforms/widgets.py:170 +#: oldforms/__init__.py:572 +msgid "Unknown" +msgstr "不明" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "操作時刻" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "オブジェクト ID" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "オブジェクトの文字列表現" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "操作種別" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "変更メッセージ" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "ログエントリ" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "ログエントリ" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "ページが見つかりません" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "申し訳ありませんが、お探しのページは見つかりませんでした。" + +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +msgid "Home" +msgstr "ホーム" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "サーバエラー" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "サーバエラー (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "サーバエラー (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"エラーが発生しました。エラーをサイトの管理者にメールで報告しましたので、近い" +"うちに修正されるはずです。しばらくお待ちください。" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "ようこそ" + +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +msgid "Documentation" +msgstr "ドキュメント" + +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +msgid "Change password" +msgstr "パスワードの変更" + +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/comments/templates/comments/form.html:6 +msgid "Log out" +msgstr "ログアウト" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django サイト管理" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django 管理サイト" + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "追加" + +#: contrib/admin/templates/admin/change_form.html:21 +#: contrib/admin/templates/admin/object_history.html:5 +msgid "History" +msgstr "履歴" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "サイト上で表示" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "下記のエラーを修正してください。" +msgstr[1] "下記のエラーを修正してください。" + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "順序" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "並び変え:" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s を追加" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "削除" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"%(object_name)s '%(escaped_object)s' の削除時に関連づけられたオブジェクトも削" +"除しようとしましたが、あなたのアカウントには以下のタイプのオブジェクトを削除" +"するパーミッションがありません:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"%(object_name)s \"%(escaped_object)s\"を削除しますか? 関連づけられている以下" +"のオブジェクトも全て削除されます:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "はい。" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "%(filter_title)s で絞り込む" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "フィルタ" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "%(name)s アプリケーションで利用可能なモデル" + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "変更" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "変更のためのパーミッションがありません。" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "最近行った操作" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "操作" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "利用不可" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"データベースの設定に問題があるようです。適切なテーブルが作られていること、適" +"切なユーザでデータベースのデータを読み込めることを確認してください。" + +#: contrib/admin/templates/admin/login.html:17 +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +msgid "Username:" +msgstr "ユーザ名:" + +#: contrib/admin/templates/admin/login.html:20 +#: contrib/comments/templates/comments/form.html:8 +msgid "Password:" +msgstr "パスワード:" + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "ログイン" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "日付/時刻" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "ユーザ" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "操作" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "Y/m/d H:i:s" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"このオブジェクトには変更履歴がありません。おそらくこの管理サイトで追加したも" +"のではありません。" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "全件表示" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "検索" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 件" +msgstr[1] "%(counter)s 件" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "全 %(full_result_count)s 件" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "新規保存" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "保存してもう一つ追加" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "保存して編集を続ける" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "保存" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"まずユーザ名とパスワードを登録してください。その後詳細情報が編集可能になりま" +"す。" + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "ユーザ名" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +msgid "Password" +msgstr "パスワード" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +msgid "Password (again)" +msgstr "パスワード(確認用)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "確認のため、再度パスワードを入力してください。" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "%(username)sさんの新しいパスワードを入力してください。" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "ブックマークレット" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "ドキュメントへのブックマークレット" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                        To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                        \n" +msgstr "" +"\n" +"

                                                                        ブックマークレットをインストールするには、リンクをブック" +"マークツールバーにドラッグするか、\n" +"リンクを右クリックしてブックマークに追加してください。これで\n" +"サイトのどのページからでもブックマークレットを選択可能になりました。\n" +"ブックマークレットによっては、内部ネットワークにあるコンピュータからこのサイ" +"トを\n" +"参照していなければならないことがあります。内部ネットワークにあるかどうか不明" +"な場合は、システム管理者に確認してください。

                                                                        \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "このページのドキュメント" + +# TODO +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "各ページから、ページを生成したビューのドキュメントにジャンプします。" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "オブジェクト ID を表示" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"単一のオブジェクトを表示するページのコンテンツタイプと一意な IDを表示します。" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "オブジェクトを (現在のウィンドウで) 編集" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "単一のオブジェクトを表示するページの管理ページへジャンプします。" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "オブジェクトを (新しいウィンドウで) 編集" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "上と同じですが、新しいウィンドウで管理ページを開きます。" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "ご利用ありがとうございました。" + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "もう一度ログイン" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "パスワードの変更" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "パスワードを変更しました" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "あなたのパスワードは変更されました" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"セキュリティ上の理由から元のパスワードの入力が必要です。新しいパスワードは正" +"しく入力したか確認できるように二度入力してください。" + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "元のパスワード:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "新しいパスワード:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "新しいパスワード (確認用) :" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "パスワードの変更" + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "パスワードをリセット" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "パスワードをリセットしました" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"登録されているメールアドレスに新しいパスワードを送信しました。まもなく届くで" +"しょう。" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"あなたのパスワードはリセットされましたので、ここにメールでご連絡差し上げま" +"す。" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "リセットされたのは %(site_name)s のアカウントです。" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "新しいパスワード: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "パスワードは下記のページで自由に変更していただけます:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "あなたのユーザ名 (念のため):" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "ご利用ありがとうございました!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr " %(site_name)s チーム" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"パスワードをお忘れですか?メールアドレスを入力してください。パスワードをリ" +"セットして、新しいパスワードをメールでお知らせします。" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "メールアドレス" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "パスワードをリセット" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "日付:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "時刻:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "現在:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "変更:" + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "いつでも" + +#: contrib/admin/views/auth.py:19 contrib/admin/views/main.py:257 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" を追加しました。" + +#: contrib/admin/views/auth.py:24 contrib/admin/views/main.py:261 +#: contrib/admin/views/main.py:347 +msgid "You may edit it again below." +msgstr "続けて編集できます。" + +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "ユーザを追加" + +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." +msgstr "パスワードを変更しました" + +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "パスワードの変更: %s" + +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"正しいユーザ名とパスワードを入力してください (大文字小文字は区別します) 。" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"再ログインしてください。ログインセッションが有効期間切れしてしまいました。入" +"力データは失われておりませんのでご安心ください。" + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"ブラウザがクッキーの使用を許可していないようです。クッキーの使用を許可して、" +"もう一度このページを表示してください。" + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "ユーザ名には '@' を含められません。" + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "メールアドレスはユーザ名ではありません。 '%s' を試してみてください。" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "タグ" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "フィルタ" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "ビュー" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "アプリケーション %r が見つかりません" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "モデル %r が %r アプリケーションに見つかりません" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "`%s.%s` (関連オブジェクト)" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "モデル :" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "`%s.%s` (関連オブジェクト)" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "全ての %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "%s の数" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "%s のフィールド" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "整数" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "ブール値 (真: True または偽: False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "文字列 ( %(maxlength)s 字まで )" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "カンマ区切りの整数" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "日付" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "日時" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "メールアドレス" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "ファイルの場所" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "10 進数 (小数可)" + +#: contrib/admin/views/doc.py:304 contrib/comments/models.py:85 +msgid "IP address" +msgstr "IP アドレス" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "ブール値 (真: True 、偽: False または None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "親モデルへのリレーション" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "電話番号" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "テキスト" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "時刻" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "アメリカの州 (大文字二文字で)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XMLテキスト" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s はurlpatternオブジェクトでは無いようです" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "サイト管理" + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "続けて別の %s を追加できます。" + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "%s を追加" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "%s を追加しました。" + +#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 db/models/manipulators.py:306 +msgid "and" +msgstr "と" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "%s を変更しました。" + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "%s を削除しました。" + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "変更はありませんでした。" + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" を変更しました。" + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" を追加しました。続けて編集できます。" + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "%s を変更" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "%(name)s に %(fieldname)s が一つ以上あります: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "%(name)s に %(fieldname)s が一つ以上あります:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" を削除しました。" + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "よろしいですか?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "変更履歴: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "%s を選択" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "変更する %s を選択" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "データベースエラー" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "確認用パスワードが一致しません。" + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "同じユーザ名が既に登録済みです。" + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"お使いのブラウザはクッキーを有効にしていないようです。ログインにはクッキーが" +"必要です。" + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "アカウントが無効です。" + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "メールアドレスの一致するユーザはいません。本当に登録しましたか?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "新しいパスワード(確認用)が一致しません。" + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "元のパスワードが間違っています。もう一度入力してください。" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "名前" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "コード名" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "パーミッション" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "パーミッション" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "グループ" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "グループ" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "ユーザ名" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"この項目は必須です。半角アルファベット、半角数字、半角アンダーバーで30文字以" +"下にしてください。" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "名" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "姓" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "メールアドレス" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "パスワード" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"'[algo]$[salt]$[hexdigest]'形式か、" +"パスワード変更フォームを使ってください。" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "スタッフ権限" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "ユーザが管理サイトにログイン可能かどうかを示します。" + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "有効" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "ユーザが管理サイトにログイン可能かどうかを示します。" + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "スーパーユーザ権限" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "全ての権限を持っているとみなされます。" + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "最終ログイン" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "登録日" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"手動で付与したパーミッションに加え、所属しているグループに付与された全ての" +"パーミッションを獲得します。" + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "ユーザパーミッション" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "ユーザ" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "ユーザ" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "個人情報" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "パーミッション" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "重要な日程" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "グループ" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "メッセージ" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "ログアウト" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "オブジェクト ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "新着情報" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "コメント" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "レーティング #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "レーティング #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "レーティング #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "レーティング #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "レーティング #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "レーティング #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "レーティング #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "レーティング #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "は有効なレーティングです" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "コメント投稿日時" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "は公開中です" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "は削除されました" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"コメントが不適切な場合はチェックを入れてください。「コメントは削除されまし" +"た」と表示されるようになります。" + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "コメント" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "コンテンツオブジェクト" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"%(user)s が %(date)s に投稿\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "名前" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP アドレス" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "スタッフの承認済み" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "フリーコメント" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "フリーコメント" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "スコア" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "スコアされた日" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "カルマスコア" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "カルマスコア" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(user)s により %(score)d 点のレーティング" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"このコメントは %(user)s がフラグ付けしました。:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "フラグ日" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "ユーザフラグ" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "ユーザフラグ" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "%r によるフラグ" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "削除日" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "モデレータ削除" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "モデレータ削除" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "%r によるモデレータ削除" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "パスワードをお忘れですか?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "レーティング" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "必須" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "オプション" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "写真を登録" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "コメント:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "コメントをプレビュー" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "ユーザ名:" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"他のレーティングを入力した場合は、このレーティングは必ず入力してください。" + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "このコメントを投稿したユーザのコメント数は %(count)s 未満です。" +msgstr[1] "このコメントを投稿したユーザのコメント数は %(count)s 未満です。" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"このコメントを投稿したユーザの詳細は不明です:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "POST メソッドのみ有効です。" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "必須項目がいくつか入力されていません。" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "だれかがコメントフォームを改竄しています (セキュリティ侵害です)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"コメントフォームの 'target' パラメータが不正です。 -- オブジェクト IDが不正な" +"値でした" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "コメントの「プレビュー」「投稿」種別が不明です。" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "非ログインユーザは投票できません。" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "コメント ID が不正です" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "自分には投票できません。" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "Python モデルクラス名" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "コンテンツタイプ" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "コンテンツタイプ" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"例: '/about/contact/'. 先頭と最後にスラッシュがあるか確認してください。" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "タイトル" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "内容" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "コメントを有効にする" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "テンプレート名" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"例: 'flatpages/contact_page.html'. 指定しなければ、デフォルト設定" +"の'flatpages/default.html' を使います。" + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "登録が必要です" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "チェックした場合、ログインしたユーザだけがページを参照できます。" + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "フラットページ" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "フラットページ" + +#: contrib/localflavor/usa/forms.py:13 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "XXXXXか、XXXXX-XXXXの形式で郵便番号を入力してください。" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "リダイレクト元" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "'/events/search/' のように、ドメイン名を除いた絶対パスにします。 " + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "リダイレクト先" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "上記のような絶対パスか、 'http://' で始まる完全な URL にします。" + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "リダイレクト" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "リダイレクト" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "セッションキー" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "セッションデータ" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "有効期限" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "セッション" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "セッション" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "ドメイン名" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "表示名" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "サイト" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "サイト" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "半角の英数字およびアンダースコア以外は使用できません。" + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"半角の英数字、アンダースコア、ダッシュ、スラッシュ以外は使用できません。" + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "半角の英数字、アンダースコア、ハイフン以外は使用できません。" + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "大文字はここでは使用できません。" + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "小文字はここでは使用できません。" + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "カンマ区切りの数字だけを入力してください。" + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "カンマ区切りの有効なメールアドレスを入力してください。" + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "有効な IP アドレスを入力してください。" + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "入力は必須です。" + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "数値以外は使用できません。" + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "数値だけの値にはできません。" + +#: core/validators.py:120 newforms/fields.py:126 +msgid "Enter a whole number." +msgstr "整数を入力してください。" + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "半角アルファベット以外使用できません。" + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "1900年以降を指定してください。" + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "無効な日付: %s" + +#: core/validators.py:147 db/models/fields/__init__.py:454 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "YYYY-MM-DD形式で日付を入力してください。" + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "HH:MM形式で時刻を入力してください。" + +#: core/validators.py:156 db/models/fields/__init__.py:521 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "YYYY-MM-DD HH:MM形式で日時を入力してください。" + +#: core/validators.py:161 newforms/fields.py:269 +msgid "Enter a valid e-mail address." +msgstr "有効なメールアドレスを入力してください。" + +#: core/validators.py:173 core/validators.py:442 oldforms/__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" +"ファイルが取得できませんでした。formのencoding typeを確認してください。" + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"画像をアップロードしてください。アップロードした画像は画像でないか、または壊" +"れています。" + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "URL ( %s ) は画像ではありません。" + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "電話番号は XXX-XXX-XXXX 形式で入力してください。\"%s\" は無効です。" + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "URL ( %s ) は QuickTime ビデオではありません。" + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "正しい URL を入力してください。" + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"有効な HTML を入力してください。エラー:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "不正な XML です: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "無効なURL: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "URL ( %s ) はリンクが壊れています。" + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "正しい米州略称を入力してください。" + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "言葉使いに気を付けて! %s という言葉は使えません。" +msgstr[1] "言葉使いに気を付けて! %s という言葉は使えません。" + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "このフィールドは '%s' フィールドと一致せねばなりません。" + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "少なくとも一つのフィールドに何か入力してください。" + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "両方のフィールドに入力するか、両方とも未入力にしてください。" + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "" +"%(field)s を %(value)s にするのなら、このフィールドに必ず入力してください。" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "" +"%(field)s を %(value)s にしないのなら、このフィールドに必ず入力してください。" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "重複する値は認められません。" + +#: core/validators.py:364 +#, python-format +msgid "This value must be between %s and %s." +msgstr "この値は %s から %s の間でなければなりません。" + +#: core/validators.py:366 +#, python-format +msgid "This value must be at least %s." +msgstr "この値は %s 以上でなければなりません。" + +#: core/validators.py:368 +#, python-format +msgid "This value must be no more than %s." +msgstr "この値は %s より小さくなければなりません。" + +#: core/validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "この値は %s の累乗でなければなりません。" + +#: core/validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "有効な 10 進数を入力してください。" + +#: core/validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "全体で %s 文字以下の数字を入力してください。" +msgstr[1] "全体で %s 文字以下の数字を入力してください。" + +#: core/validators.py:422 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "整数部は %s 文字以下の数字を入力してください。" +msgstr[1] "整数部は %s 文字以下の数字を入力してください。" + +#: core/validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "小数部は %s 文字以下の数字を入力してください。" +msgstr[1] "小数部は %s 文字以下の数字を入力してください。" + +#: core/validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "アップロードするファイルの大きさは %s バイト以上にしてください。" + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "アップロードするファイルの大きさは %s 最大バイトまでです。" + +#: core/validators.py:453 +msgid "The format for this field is wrong." +msgstr "フィールドの形式が正しくありません。" + +#: core/validators.py:468 +msgid "This field is invalid." +msgstr "このフィールドは無効です。" + +#: core/validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "%s から何も検索できませんでした。" + +#: core/validators.py:507 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"URL %(url)s は無効なコンテンツタイプヘッダ '%(contenttype)s' を返しました。" + +#: core/validators.py:540 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"%(line)s 行目から始まる %(tag)s タグを閉じてください (\"%(start)s\" で始まる" +"行です)。" + +#: core/validators.py:544 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"%(line)s 行目から始まるテキストはこのコンテキストでは使えません。 (\"%(start)" +"s\" で始まる行です)。" + +#: core/validators.py:549 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"%(line)s 行目の \"%(attr)s\" は無効なアトリビュートです (\"%(start)s\" で始ま" +"る行です)。" + +#: core/validators.py:554 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"%(line)s 行目の \"<%(tag)s>\" は無効なタグです( \"%(start)s\" で始まる行で" +"す)。" + +#: core/validators.py:558 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"%(line)s 行目のタグは必須アトリビュートが未入力です( \"%(start)s\" で始まる行" +"です)。" + +#: core/validators.py:563 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"%(line)s 行目の \"%(attr)s\" アトリビュートの値が正しくありません (\"%(start)" +"s\" で始まる行です) 。" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" +"%(field)s に入力されたものは、この %(type)s の %(object)s に既に存在します。" + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(fieldname)s に %(optname)s は既に存在します。" + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: newforms/fields.py:78 newforms/fields.py:373 newforms/fields.py:449 +#: newforms/fields.py:460 oldforms/__init__.py:352 +msgid "This field is required." +msgstr "このフィールドは必須です。" + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "値は整数でなければなりません。" + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "値は真: True または偽: False でなければなりません。" + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "このフィールドには NULL を指定できません。" + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "正しいファイル名を入力してください。" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "正しい %s を入力してください。" + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "複数の ID はカンマで区切ってください。" + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"複数選択するときには Control キーを押したまま選択してください。Mac は " +"Command キーを使ってください" + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "正しい %(self)s IDを入力してください。 %(value)r は無効です。" +msgstr[1] "正しい %(self)s IDを入力してください。 %(value)r は無効です。" + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "%d 字以下で入力してください。" + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "%d 字以上で入力してください。" + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "この値は %s 以下でなければなりません。" + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "この値は %s 以上でなければなりません。" + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "日付を正しく入力してください。" + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "時間を正しく入力してください。" + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "日付/時間を正しく入力してください。" + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "値を正しく入力してください。" + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "URLを正しく入力してください。" + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "このURLはリンクが壊れています。" + +#: newforms/fields.py:359 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "正しく選択してください。選択したものは候補にありません。" + +#: newforms/fields.py:377 newforms/fields.py:453 +msgid "Enter a list of values." +msgstr "リストを入力してください。" + +#: newforms/fields.py:386 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "正しく選択してください。 %s は候補にありません。" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "%s 字以下で入力してください。" +msgstr[1] "%s 字以下で入力してください。" + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "改行はできません。" + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "正しく選択してください。; '%(data)s' は %(choices)s にありません。" + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "入力されたファイルは空です。" + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "-32,768 から 32,767 までの整数を入力してください。" + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "正の数を入力してください。" + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "0 から 32,767 までの整数を入力してください。" + +#: template/defaultfilters.py:436 +msgid "yes,no,maybe" +msgstr "はい,いいえ,たぶん" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "月曜日" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "火曜日" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "水曜日" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "木曜日" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "金曜日" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "土曜日" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "日曜日" + +#: utils/dates.py:14 +msgid "January" +msgstr "1月" + +#: utils/dates.py:14 +msgid "February" +msgstr "2月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "3月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "4月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "5月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "6月" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "7月" + +#: utils/dates.py:15 +msgid "August" +msgstr "8月" + +#: utils/dates.py:15 +msgid "September" +msgstr "9月" + +#: utils/dates.py:15 +msgid "October" +msgstr "10月" + +#: utils/dates.py:15 +msgid "November" +msgstr "11月" + +#: utils/dates.py:16 +msgid "December" +msgstr "12月" + +#: utils/dates.py:19 +msgid "jan" +msgstr "1月" + +#: utils/dates.py:19 +msgid "feb" +msgstr "2月" + +#: utils/dates.py:19 +msgid "mar" +msgstr "3月" + +#: utils/dates.py:19 +msgid "apr" +msgstr "4月" + +#: utils/dates.py:19 +msgid "may" +msgstr "5月" + +#: utils/dates.py:19 +msgid "jun" +msgstr "6月" + +#: utils/dates.py:20 +msgid "jul" +msgstr "7月" + +#: utils/dates.py:20 +msgid "aug" +msgstr "8月" + +#: utils/dates.py:20 +msgid "sep" +msgstr "9月" + +#: utils/dates.py:20 +msgid "oct" +msgstr "10月" + +#: utils/dates.py:20 +msgid "nov" +msgstr "11月" + +#: utils/dates.py:20 +msgid "dec" +msgstr "12月" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "1月" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "2月" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "8月" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "9月" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "10月" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "11月" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "12月" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "年" +msgstr[1] "年" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "月" +msgstr[1] "月" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "週" +msgstr[1] "週" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "日" +msgstr[1] "日" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "時" +msgstr[1] "時" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "分" +msgstr[1] "分" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "Y/m/d" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "Y/m/d H:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "Y/m/d" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "m/d" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s を作成しました。" + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s を更新しました。" + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr " %(verbose_name)s を削除しました。" diff --git a/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/ja/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..bddecacb108c1580bc2d408c658ad30deadb7451 GIT binary patch literal 1604 zcwS|aL2nyH6o3aDhZ3NL00jvSb14EVFDsC8<2kuR&x_(O42~+w$;Oj8M zsg&yX*FIJ6ci;|SNAoYu{;efAP=GcuctL1 z2YFK4@(rIs>a7R051u-i^$ER~b3TIZTRBXI{2?@E+h}ZoxE`7HDBcyKapKXyCLD~w zq8=KrFcwlwuyB2PZ|5ZkAq|ogj}oqY9<%1rYpzYvGzlWYYj~2*^28_-ZdLGDmAf9; zH9Q>!6qHxPW04bu0+(o2P=A(ke8dt=@-SOajtOcd1}eIC;5rq74l@>jdK=A9AEHnR z7jYRM2bshrRH7cW1g^1&W2?eki@q4Xgh^my%X6)Hj7k+qEk!UWG{zE?BN|E`r*@#M zawX!4G09zt5)lyx1C^Q5KI0tfoS>ng$-z<1=HOyu$~KNs9=a?j;E6dBI1Fco%z+tN zWn37=&~a_!)yN5rGArPLqesfdIHyEFyfHxpE#N(-nKLq`Vdk)D7P97UbHp?u(3qhM zZg^juxz8~3IFl`8M^xM!Bos!OlOXhnV7!2RGS39VB8+O$yo*PDZ5?!c`1C^-^AA8$ z;6%isMwt@7fTu~7z&lVpY9|92bL61InB!50v#1)Xq~cWZ(AKDO#&H%18o>3nD)cAl zJ7T!%5jS`RJ_O+*75gJ`(#UVc$u>_?ZX60MX1jq?!1-BM450SerS|EwIEOoz>S*)l z+wJA6sJ-+}_mfup+>On~ZTaWL_0_oXUEKISZoC&aeuVv6+_)z9^^Lf(iXgT3>Bn*X zqu%1J&hll{e!mqjUe_Cic=1A9KNBxrk@eJhT)z=FzK!c&$^6aM66$?%dGpIwcjX6^ z=`@${9;vcY?UibuR5__$mMSmRh*TLf(Me85CZYC9^X{!R)LFX*)1mIUpW4k9YM);2 zU0Ok%i(g|I*, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-10-06 00:30+0900\n" +"PO-Revision-Date: 2006-05-08 13:39+0900\n" +"Last-Translator: makoto tsuyuki \n" +"Language-Team: Japanese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/calendar.js:24 +#: contrib/admin/media/js/dateparse.js:32 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "日 月 火 水 木 金 土" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "日曜 月曜 火曜 水曜 木曜 金曜 土曜" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "利用可能 %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "全て選択" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "追加" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "削除" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "選択された %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "選択してクリック" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "全てクリア" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "表示" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "非表示" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "現在" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "時計" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "時間を選択" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "夜中" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "午前 6 時" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "正午" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "キャンセル" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "今日" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "カレンダー" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "昨日" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "明日" diff --git a/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..51e9ab5fbc2aea2af6d204b21f00a9b5c144009a GIT binary patch literal 57878 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 Wx`}`iCRR8LRkkMFOm=EQ=Kleiy0NAJ literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.po new file mode 100644 index 0000000..b8adeb2 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/django.po @@ -0,0 +1,2533 @@ +# Kannada translation of Django. +# Copyright (C) 2007 Translation Team +# This file is distributed under the same license as the Django package. +# Kannada Localization Team , 2007. +# +# +msgid "" +msgstr "" +"Project-Id-Version: Django-kn 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-09-25 15:43+0200\n" +"PO-Revision-Date: 2007-01-08 20:22+0530\n" +"Last-Translator: Kannada Localization Team \n" +"Language-Team: Kannada \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ವಸ್ತುವಿನ ಐಡಿ" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "ತಲೆಬರಹ" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "ಟಿಪ್ಪಣಿ" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "ಕ್ರಮಾಂಕ ೧" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "ಕ್ರಮಾಂಕ ೨" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "ಕ್ರಮಾಂಕ ೩ " + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "ಕ್ರಮಾಂಕ ೪" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "ಕ್ರಮಾಂಕ ೫ " + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "ಕ್ರಮಾಂಕ ೬ " + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "ಕ್ರಮಾಂಕ ೭" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "ಕ್ರಮಾಂಕ ೮" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "ಕ್ರಮಬದ್ಧ ಕ್ರಮಾಂಕ" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "ಸಲ್ಲಿಸಿದ ದಿನಾಂಕ/ಸಮಯ" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "ಸಾರ್ವಜನಿಕವಾಗಿದೆ" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "IP ವಿಳಾಸ" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "ತೆಗೆದು ಹಾಕಲಾಗಿದೆ" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"ಟಿಪ್ಪಣಿ ಅನುಚಿತವಾಗಿ " +"ಇದ್ದಲ್ಲಿ ಈ ಚೌಕದಲ್ಲಿ ಗುರುತು " +"ಮಾಡಿ. ಅದರ ಬದಲಾಗಿ \"ಈ ಟಿಪ್ಪಣಿ " +"ತೆಗೆದುಹಾಕಲಾಗಿದೆ\" ಎಂಬ " +"ಸಂದೇಶವನ್ನು ತೋರಿಸಲಾಗುವುದು." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "ಟಿಪ್ಪಣಿಗಳು" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "ಒಳವಿಷಯ ವಸ್ತು" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"ಸಲ್ಲಿಸಿದವರು %(user)s ರವರು %(date)s\n" +"\n" +" ದಿನ/ಸಮಯಕ್ಕೆ %(comment)s\n" +"\n" +"http://%(domain)s%(url)s ಸಲ್ಲಿಸಿದ್ದು" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "ವ್ಯಕ್ತಿಯ ಹೆಸರು" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "IP ವಿಳಾಸ" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" +"ಸಿಬ್ಬಂದಿಯಿಂದ ಅನುಮೋದನೆ " +"ಪಡೆದಿದೆ" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "ಉಚಿತ ಟಿಪ್ಪಣಿ" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "ಉಚಿತ ಟಿಪ್ಪಣಿಗಳು" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "ಅಂಕ" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "ಅಂಕದ ದಿನಾಂಕ" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "ಕರ್ಮ ಅಂಕ" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "ಕರ್ಮ ಅಂಕಗಳು" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr " %(user)s ಇಂದ %(score)d ಕ್ರಮಾಂಕ" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"ಈ ಟಿಪ್ಪಣಿಯನ್ನು %(user)s ರವರು " +"ಪತಾಕೆಯಿಂದ ಗುರುತು " +"ಮಾಡಿದ್ದಾರೆ:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "ಪತಾಕೆ ದಿನಾಂಕ" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "ಬಳಕೆದಾರ ಪತಾಕೆ" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "ಬಳಕೆದಾರ ಪತಾಕೆಗಳು" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "%r ಇಂದ ಪತಾಕೆ" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "ತೆಗೆದುಹಾಕಿದ ದಿನಾಂಕ" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "ನಿಯಂತ್ರಕರು ಅಳಿಸಿದ್ದು" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "ನಿಯಂತ್ರಕರು ಅಳಿಸಿದ್ದು" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr " %r ಇಂದ ನಿಯಂತ್ರಕರ ಅಳಿಸುವಿಕೆ " + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "" +"ಅನಾಮಧೇಯ ಬಳಕೆದಾರರು ಮತ " +"ಹಾಕುವಂತಿಲ್ಲ" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ತಪ್ಪು ಟಿಪ್ಪಣಿ ಐಡಿ" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "" +"ತಮಗೇ ಮತ ಹಾಕಿಕೊಳ್ಳುವಂತಿಲ್ಲ." + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"ನೀವು ಬೇರೆಯ ಕ್ರಮಾಂಕ " +"ನೀಡಿರುವುದರಿಂದ ಈ ಕ್ರಮಾಂಕ " +"ಅವಶ್ಯವಾಗಿದೆ." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr "" +"%(count)s ಕ್ಕಿಂತಲೂ ಕಡಿಮೆ " +"ಟಿಪ್ಪಣಿಗಳನ್ನು ಬರೆದಿರುವ " +"ಸದಸ್ಯರಿಂದ ಈ ಟಿಪ್ಪಣಿ " +"ಬರೆಯಲ್ಪಟ್ಟಿದೆ:\n" +"\n" +"%(text)s" +"%(count)sಕ್ಕಿಂತಲೂ ಕಡಿಮೆ " +"ಟಿಪ್ಪಣಿಗಳನ್ನು ಬರೆದಿರುವ " +"ಸದಸ್ಯರಿಂದ ಈ ಟಿಪ್ಪಣಿ " +"ಬರೆಯಲ್ಪಟ್ಟಿದೆ:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"ಈ ಟಿಪ್ಪಣಿಯನ್ನು ಅಪೂರ್ಣ " +"ಮಾಹಿತಿಯುಳ್ಳ ಬಳಕೆದಾರ :\n" +"\n" +" %(text)s ರು ಸಲ್ಲಿಸಿದ್ದಾರೆ" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "" +"ಸಲ್ಲಿಕೆಗಳಿಗೆ ಮಾತ್ರ " +"ಅನುಮತಿಯಿದೆ" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "" +"ಒಂದು ಅಥವಾ ಹೆಚ್ಚು ಅಗತ್ಯ " +"ಅಂಶಗಳನ್ನು ಸಲ್ಲಿಸಿಲ್ಲ" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" +"ಯಾರೋ ಟಿಪ್ಪಣಿಯನ್ನು " +"ಬದಲಾಯಿಸಿದ್ದಾರೆ( ಭದ್ರತೆಯ " +"ಉಲ್ಲಂಘನೆ)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"ಟಿಪ್ಪಣಿ ನಮೂನೆಗೆ ತಪ್ಪು " +"ಟಾರ್ಗೆಟ್ ಪ್ಯಾರಾಮೀಟರ್ ಇದೆ. " +"ವಸ್ತುವಿನ ಐಡಿಯು " +"ದೋಷಪೂರಿತವಾಗಿತ್ತು." + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" +"ಟಿಪ್ಪಣಿ ನಮೂನೆ " +"'ಮುನ್ನೋಟ'ವನ್ನಾಗಲೀ " +"'ಸಲ್ಲಿಕೆ'ಯನ್ನಾಗಲೀ " +"ಒದಗಿಸಲಿಲ್ಲ." + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "ಬಳಕೆದಾರನ ಹೆಸರು:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "ಹೊರಕ್ಕೆ ಹೋಗಿ" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "ಪ್ರವೇಶಪದ:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "" +"ನಿಮ್ಮ ಪ್ರವೇಶಪದ ಮರೆತಿದ್ದೀರಾ?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "ಕ್ರಮಾಂಕಗಳು" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "ಅವಶ್ಯ" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "ಐಚ್ಛಿಕ" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "ಭಾವಚಿತ್ರ ಸಲ್ಲಿಸಿ" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "ಟಿಪ್ಪಣಿ:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "ಟಿಪ್ಪಣಿ ಮುನ್ನೋಟ" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "ನಿಮ್ಮ ಹೆಸರು:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                        By %s:

                                                                        \n" +"
                                                                          \n" +msgstr "" +"

                                                                          %s ಇಂದ :

                                                                          \n" +"
                                                                            \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "ಎಲ್ಲಾ" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "ಯಾವುದೇ ದಿನಾಂಕ" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "ಈದಿನ" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "ಕಳೆದ ೭ ದಿನಗಳು" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "ಈ ತಿಂಗಳು" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "ಈ ವರ್ಷ" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "ಹೌದು" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "ಇಲ್ಲ" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "ಗೊತ್ತಿಲ್ಲ(ದ/ದ್ದು)" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "ಕ್ರಮದ(ಕ್ರಿಯೆಯ) ಸಮಯ" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "ವಸ್ತುವಿನ ಐಡಿ" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "ವಸ್ತು ಪ್ರಾತಿನಿಧ್ಯ" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "ಕ್ರಮದ(ಕ್ರಿಯೆಯ) ಪತಾಕೆ" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "" +"ಬದಲಾವಣೆಯ ಸಂದೇಶ/ಸಂದೇಶ ಬದಲಿಸಿ" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "ಲಾಗ್ ದಾಖಲೆ" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "ಲಾಗ್ ದಾಖಲೆಗಳು" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "ಎಲ್ಲಾ ದಿನಾಂಕಗಳು" + +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:59 +msgid "" +"Please enter a correct username and password. Note that both fields are " +"case-sensitive." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ಬಳಕೆದಾರ-ಪದ " +"ಮತ್ತು ಪ್ರವೇಶಪದ ಬರೆಯಿರಿ " +".ಎರಡೂ ಇಂಗ್ಲೀಷಿನ ಸಣ್ಣ ಮತ್ತು " +"ದೊಡ್ಡ ಅಕ್ಷರ ಸಂವೇದಿ " +"ಎಂಬುದನ್ನು ಗಮನದಲ್ಲಿಡಿ" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "ಒಳಗೆ ಬನ್ನಿ" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"ದಯವಿಟ್ಟು ಇನ್ನೊಮ್ಮೆ ಒಳಬನ್ನಿ " +"(ಲಾಗಿನ್ ಮಾಡಿ) . ನಿಮ್ಮ ಅಧಿವೇಶನ " +"ಕೊನೆಗೊಂಡಿದೆ. " +"ಚಿಂತಿಸಬೇಡಿ:ನಿಮ್ಮ " +"ಸಲ್ಲಿಕೆಯನ್ನು ಉಳಿಸಲಾಗಿದೆ." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"ಕುಕೀಗಳನ್ನು ಸ್ವೀಕರಿಸುವಂತೆ " +"ನಿಮ್ಮ ಜಾಲವೀಕ್ಷಕವನ್ನು " +"ಸಂರಚಿಸಲಾಗಿಲ್ಲ ಎಂದು " +"ತೋರುತ್ತದೆ. ದಯವಿಟ್ಟು " +"ಕುಕೀಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ , ಈ " +"ಪುಟವನ್ನು ಮತ್ತೆ ಲೋಡ್ ಮಾಡಿ " +"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿರಿ. " + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "" +"ಬಳಕೆದಾರ-ಹೆಸರುಗಳು '@' " +"ಅಕ್ಷರವನ್ನು ಒಳಗೊಳ್ಳುವಂತಿಲ್ಲ" + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "" +"ನಿಮ್ಮ ವಿ-ಅಂಚೆ ವಿಳಾಸವು ನಿಮ್ಮ " +"ಬಳಕೆದಾರ-ಹೆಸರಲ್ಲ; ಬದಲಾಗಿ '%s' " +"ಪ್ರಯತ್ನಿಸಿ." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "ತಾಣ ನಿರ್ವಹಣೆ" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:17 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "" +" %(name)s \"%(obj)s\" ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ " +"ಸೇರಿಸಲಾಯಿತು." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:22 +msgid "You may edit it again below." +msgstr "" +"ನೀವು ಅದನ್ನು ಕೆಳಗೆ ಮತ್ತೆ " +"ಬದಲಾಯಿಸಬಹುದು." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "" +"ನೀವು ಕೆಳಗೆ ಇನ್ನೊಂದು %s " +"ಸೇರಿಸಬಹುದು." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "%s ಸೇರಿಸಿ" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "%s ಸೇರಿಸಲಾಯಿತು." + +#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 +msgid "and" +msgstr "ಮತ್ತು" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "%s ಬದಲಾಯಿಸಲಾಯಿತು." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "%s ತೆಗೆದುಹಾಕಲಾಯಿತು." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "ಯಾವುದೇ ಅಂಶಗಳು ಬದಲಾಗಲಿಲ್ಲ." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "" +"%(name)s \"%(obj)s\" ಸಫಲವಾಗಿ " +"ಬದಲಾಯಿಸಲಾಯಿತು." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"%(name)s \"%(obj)s\" ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ " +"ಸೇರಿಸಲಾಯಿತು. ನೀವು ಕೆಳಗೆ " +"ಅದನ್ನು ಮತ್ತೆ ಬದಲಾಯಿಸಬಹುದು." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "%s ಅನ್ನು ಬದಲಿಸು" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" +"%(name)s ನಲ್ಲಿ ಒಂದು ಅಥವಾ ಹೆಚ್ಚು " +"%(fieldname)s :%(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" +"%(name)s ನಲ್ಲಿ ಒಂದು ಅಥವಾ ಹೆಚ್ಚು " +"%(fieldname)s :" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "" +"%(name)s \"%(obj)s\" ಯಶಸ್ವಿಯಾಗಿ " +"ಅಳಿಸಲಾಯಿತು." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "ಖಚಿತಪಡಿಸುವಿರಾ? " + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "ಬದಲಾವಣೆಗಳ ಇತಿಹಾಸ: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "%s ಆಯ್ದುಕೊಳ್ಳಿ" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "ಬದಲಾಯಿಸಲು %s ಆಯ್ದುಕೊಳ್ಳಿ" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "ದತ್ತಸಂಚಯದ ದೋಷ" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "ಟ್ಯಾಗ್:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "ಸೋಸಕ:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "ನೋಟ:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "%r ಅನ್ವಯಾಂಶ ಸಿಗಲಿಲ್ಲ" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "" +"%r ಅನ್ವಯಾಂಶದಲ್ಲಿ %r ಮಾಡೆಲ್ಲು " +"ಸಿಗಲಿಲ್ಲ" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "ಸಂಬಂಧಿಸಿದ `%s.%s` ವಸ್ತು" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "ಮಾಡೆಲ್:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "ಸಂಬಂಧಿಸಿದ `%s.%s` ವಸ್ತುಗಳು" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "ಎಲ್ಲಾ %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "%s ಗಳ ಸಂಖ್ಯೆ" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "%s ವಸ್ತುಗಳ ಅಂಶಗಳು" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "ಸಂಖ್ಯೆ" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "ಬೂಲಿಯನ್( ನಿಜ ಅಥವಾ ಸುಳ್ಳು)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "(%(maxlength)s ವರೆಗಿನ ) ಅಕ್ಷರಪುಂಜ" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "" +"ಅಲ್ಪವಿರಾಮ(,) ದಿಂದ ಬೇರ್ಪಟ್ಟ " +"ಸಂಖ್ಯೆಗಳು" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "ದಿನಾಂಕ (ಸಮಯರಹಿತ)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "ದಿನಾಂಕ(ಸಮಯದೊಂದಿಗೆ)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "ವಿ-ಅಂಚೆ ವಿಳಾಸ" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "ಕಡತದ ಸ್ಥಾನಪಥ" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "ದಶಮಾನ ಸಂಖ್ಯೆ" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "" +"ಬೂಲಿಯನ್( ನಿಜ ಅಥವಾ ಸುಳ್ಳು " +"ಅಥವಾ ಯಾವುದೂ ಅಲ್ಲ)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "" +"ಹಿರಿಯ ಮಾಡೆಲ್‍ನೊಂದಿಗಿನ ಸಂಬಂಧ" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "ದೂರವಾಣಿ ಸಂಖ್ಯೆ" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "ಪಠ್ಯ" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "ಸಮಯ" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "" +"ಅಮೇರಿಕಾ ಸಂಯುಕ್ತ ಸಂಸ್ಥಾನದ " +"ರಾಜ್ಯ ( ಎರಡು ಇಂಗ್ಲೀಷ್ " +"ದೊಡ್ಡಕ್ಷರಗಳು)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML ಪಠ್ಯ" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "" +"%s URL ಸ್ವರೂಪದ್ದಾಗಿ ತೋರುವದಿಲ್ಲ." + +#: contrib/admin/views/auth.py:28 +msgid "Add user" +msgstr "ಬಳಕೆದಾರನನ್ನು ಸೇರಿಸಿ" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "ವಿವರಮಾಹಿತಿ" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "ಪ್ರವೇಶಪದ ಬದಲಿಸಿ" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "ಪ್ರಾರಂಭಸ್ಥಳ(ಮನೆ)" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "ಚರಿತ್ರೆ" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "ದಿನಾಂಕ/ಸಮಯ" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "ಬಳಕೆದಾರ" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "ಕ್ರಮ(ಕ್ರಿಯೆ)" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"ಈ ವಸ್ತುವಿಗೆ ಬದಲಾವಣೆಯ " +"ಇತಿಹಾಸವಿಲ್ಲ. ಅದು ಬಹುಶಃ ಈ " +"ಆಡಳಿತತಾಣದ ಮೂಲಕ " +"ಸೇರಿಸಲ್ಪಟ್ಟಿಲ್ಲ." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "ಜಾಂಗೋ ತಾಣದ ಆಡಳಿತಗಾರರು" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "ಜಾಂಗೋ ಆಡಳಿತ" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "ಸರ್ವರ್ ದೋಷ" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "ಸರ್ವರ್ ದೋಷ(೫೦೦)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "ಸರ್ವರ್ ದೋಷ(೫೦೦)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via " +"e-mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"ಇಲ್ಲಿ ಒಂದು ತಪ್ಪಾಗಿದೆ. ಅದನ್ನು " +"ತಾಣದ ಆಡಳಿತಗಾರರಿಗೆ ವರದಿ " +"ಮಾಡಲಾಗಿದ್ದು ಶೀಘ್ರದ್ದಲ್ಲಿ " +"ಸರಿಪಡಿಸಲಾಗುವದು. ನಿಮ್ಮ " +"ತಾಳ್ಮೆಗೆ ಧನ್ಯವಾದಗಳು." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "ಪುಟ ಸಿಗಲಿಲ್ಲ" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "" +"ಕ್ಷಮಿಸಿ, ನೀವು ಕೇಳಿದ ಪುಟ " +"ಸಿಗಲಿಲ್ಲ" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" +"%(name)s ಅನ್ವಯಾಂಶದಲ್ಲಿ " +"ಮಾಡೆಲ್ಲುಗಳು ಲಭ್ಯ." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "ಸೇರಿಸಿ" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "ಬದಲಿಸಿ/ಬದಲಾವಣೆ" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "" +"ಯಾವುದನ್ನೂ ತಿದ್ದಲು ನಿಮಗೆ " +"ಅನುಮತಿ ಇಲ್ಲ ." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "ಇತ್ತೀಚಿನ ಕ್ರಮಗಳು" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "ನನ್ನ ಕ್ರಮಗಳು" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "ಯಾವುದೂ ಲಭ್ಯವಿಲ್ಲ" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s ಸೇರಿಸಿ" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "" +"ನೀವು ಪ್ರವೇಶಪದವನ್ನು " +"ಮರೆತಿದ್ದೀರಾ?" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "ಸುಸ್ವಾಗತ." + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "ಅಳಿಸಿಹಾಕಿ" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"'%(escaped_object)s' %(object_name)s ಅನ್ನು " +"ತೆಗೆದುಹಾಕುವುದರಿಂದ ಸಂಬಂಧಿತ " +"ವಸ್ತುಗಳೂ ಕಳೆದುಹೋಗುತ್ತವೆ. " +"ಆದರೆ ನಿಮ್ಮ ಖಾತೆಗೆ ಕೆಳಕಂಡ " +"ಬಗೆಗಳ ವಸ್ತುಗಳನ್ನು " +"ತೆಗೆದುಹಾಕಲು ಅನುಮತಿಯಿಲ್ಲ." + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"ದಿಟವಾಗಿಯೂ, ನೀವು %(object_name)s " +"\"%(escaped_object)ಗಳನ್ನು\"? " +"ತೆಗೆದುಹಾಕಬಯಸಿದ್ದೀರಾ? " +"ಸಂಬಂಧಪಟ್ಟ ಕೆಳಕಂಡ ಎಲ್ಲವನ್ನೂ " +"ತೆಗೆದುಹಾಕಲಾಗುತ್ತದೆ:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "ಹೌದು,ನನಗೆ ಖಚಿತವಿದೆ" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "%(filter_title)s ಇಂದ" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "ಹೋಗಿ" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgstr "೧ ಫಲಿತಾಂಶ" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "ಒಟ್ಟು %(full_result_count)s" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "ಎಲ್ಲವನ್ನೂ ತೋರಿಸು" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "ಸೋಸಕ" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "ತಾಣದಲ್ಲಿ ನೋಡಿ" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgstr "" +"ದಯಮಾಡಿ ಕೆಳಗಿನ ತಪ್ಪನ್ನು " +"ಸರಿಪಡಿಸಿ " +"ದಯಮಾಡಿ ಕೆಳಗಿನ ತಪ್ಪುಗಳನ್ನು " +"ಸರಿಪಡಿಸಿ. " + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "ಅನುಕ್ರಮದಲ್ಲಿ ಜೋಡಣೆ" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "ಅನುಕ್ರಮ:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "ಹೊಸದರಂತೆ ಉಳಿಸಿ" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "" +"ಉಳಿಸಿ ಮತ್ತು ಇನ್ನೊಂದನ್ನು " +"ಸೇರಿಸಿ" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "" +"ಉಳಿಸಿ ಮತ್ತು ತಿದ್ದುವುದನ್ನು " +"ಮುಂದುವರಿಸಿರಿ." + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "ಉಳಿಸಿ" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"ಡಾಟಾಬೇಸನ್ನು ಇನ್ಸ್ಟಾಲ್ " +"ಮಾಡುವಾಗ ಏನೋ ತಪ್ಪಾಗಿದೆ. ಸೂಕ್ತ " +" ಡಾಟಾಬೇಸ್ ಕೋಷ್ಟಕಗಳು " +"ರಚನೆಯಾಗಿ ಅರ್ಹ ಬಳಕೆದಾರರು " +"ಅವುಗಳನ್ನು ಓದಬಹುದಾಗಿದೆಯೇ " +"ಎಂಬುದನ್ನು ಖಾತರಿ " +"ಪಡಿಸಿಕೊಳ್ಳಿ." + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"ಮೊದಲು ಬಳಕೆದಾರ-ಹೆಸರು ಮತ್ತು " +"ಪ್ರವೇಶಪದವನ್ನು ಕೊಡಿರಿ. ನಂತರ, " +"ನೀವು ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳನ್ನು " +"ಬದಲಿಸಬಹುದಾಗಿದೆ." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "ಬಳಕೆದಾರ-ಹೆಸರು" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "ಪ್ರವೇಶಪದ" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "ಪ್ರವೇಶಪದ(ಇನ್ನೊಮ್ಮೆ)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "" +"ಖಚಿತಗೊಳಿಸಲು ಮೇಲಿನ " +"ಪ್ರವೇಶಪದವನ್ನು ಇನ್ನೊಮ್ಮೆ " +"ಬರೆಯಿರಿ." + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "ಪ್ರವೇಶಪದ ಬದಲಾವಣೆ" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "ಪ್ರವೇಶಪದ ಬದಲಾವಣೆ ಯಶಸ್ವಿ" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "" +"ನಿಮ್ಮ ಪ್ರವೇಶಪದ " +"ಬದಲಾಯಿಸಲಾಗಿದೆ" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "ಪ್ರವೇಶಪದವನ್ನು ಬದಲಿಸುವಿಕೆ" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"ಪ್ರವೇಶಪದವನ್ನು ಮರೆತಿದ್ದೀರಾ? " +"ನಿಮ್ಮ ವಿ-ಅಂಚೆಯ ವಿಳಾಸವನ್ನು " +"ಕೆಳಗೆ ಸೂಚಿಸಿರಿ, ನಾವು ನಿಮ್ಮ " +"ಪ್ರವೇಶಪದವನ್ನು ಬದಲಾಯಿಸಿ " +"ಅದನ್ನು ರವಾನಿಸುತ್ತೇವೆ." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "ವಿ-ಅಂಚೆ ವಿಳಾಸ:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "" +"ನನ್ನ ಪ್ರವೇಶಪದವನ್ನು ಮತ್ತೆ " +"ನಿರ್ಧರಿಸಿ " + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" +"ಈದಿನ ತಮ್ಮ ಅತ್ಯಮೂಲ್ಯವಾದ " +"ಸಮಯವನ್ನು ನಮ್ಮ ತಾಣದಲ್ಲಿ " +"ಕಳೆದುದಕ್ಕಾಗಿ ಧನ್ಯವಾದಗಳು." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "ಮತ್ತೆ ಒಳಬನ್ನಿ" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "" +"ಪ್ರವೇಶಪದದ ಮರುನಿರ್ಧಾರ " +"ಸಾಧ್ಯವಾಗಿದೆ" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"ನಾವು ಹೊಸ ಪ್ರವೇಶಪದವನ್ನು " +"ನಿಮ್ಮ ವಿ-ಅಂಚೆಗೆ ಕಳಿಸಿದ್ದೇವೆ. " +"ಕೆಲವೇ ಕ್ಷಣಗಳಲ್ಲಿ ನೀವದನ್ನು " +"ಪಡೆಯಲಿದ್ದೀರಿ." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"ಭದ್ರತೆಯ ದೃಷ್ಟಿಯಿಂದ " +"ದಯವಿಟ್ಟು ನಿಮ್ಮ ಹಳೆಯ " +"ಪ್ರವೇಶಪದವನ್ನು ಸೂಚಿಸಿರಿ. " +"ಆನಂತರ ನೀವು ಸರಿಯಾಗಿ " +"ಬರೆದಿದ್ದೀರೆಂದು ನಾವು " +"ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು ಹೊಸ " +"ಪ್ರವೇಶಪದವನ್ನು ಎರಡು ಬಾರಿ " +"ಬರೆಯಿರಿ." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "ಹಳೆಯ ಪ್ರವೇಶಪದ:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "ಹೊಸ ಪ್ರವೇಶಪದ:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "ಪ್ರವೇಶಪದವನ್ನು ಖಚಿತಪಡಿಸಿ:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "ನನ್ನ ಪ್ರವೇಶಪದ ಬದಲಿಸಿ" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"ಪ್ರವೇಶಪದದ ಮರುನಿರ್ಧಾರವನ್ನು " +"ನೀವು ಕೇಳಿದುದರಿಂದ ಈ " +"ವಿ-ಅಂಚೆಯನ್ನು " +"ಪಡೆಯುತ್ತಿದ್ದೀರಿ." + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "" +"%(site_name)s ತಾಣದಲ್ಲಿ ನಿಮ್ಮ " +"ಬಳಕೆದಾರ-ಖಾತೆಗಾಗಿ" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "ನಿಮ್ಮ ಹೊಸ ಪ್ರವೇಶಪದ : %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "" +"ನಿಸ್ಸಂಕೋಚವಾಗಿ ಈ ಪುಟಕ್ಕೆ " +"ಹೋಗಿ ಈ ಪ್ರವೇಶಪದವನ್ನು " +"ಬದಲಿಸಿರಿ." + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "" +"ನೀವು ಮರೆತಿದ್ದಲ್ಲಿ , ನಿಮ್ಮ " +"ಬಳಕೆದಾರ-ಹೆಸರು" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "" +"ನಮ್ಮ ತಾಣವನ್ನು " +"ಬಳಸಿದ್ದಕ್ದಾಗಿ ಧನ್ಯವಾದಗಳು!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s ತಂಡ" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "ಚಿಕ್ಕ ಪುಟಗುರುತುಗಳು" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "" +"ಮಾಹಿತಿಯ ಚಿಕ್ಕ ಪುಟಗುರುತುಗಳು" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                            To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                            \n" +msgstr "" +"\n" +"

                                                                            ಸಣ್ಣ್ಪುಟಗುರುತುಗಳನ್ನು " +"ಅನುಸ್ಥಾಪಿಸಲು ಕೊಂಡಿಯನ್ನು " +"ಪುಟಗುರುತು ಉಪಕರಣಪಟ್ಟಿ(ಟೂಲ್ " +"ಬಾರ್)ಕಡೆಗೆ ಎಳೆದೊಯ್ಯಿರಿ,\n" +" ಅಥವಾ ಕೊಂಡಿಯ ಮೇಲೆ " +"ಮೂಷಿಕ(ಮೌಸ್)ದ ಬಲಬಟನ್ನನ್ನು " +"ಒತ್ತಿ ಪುಟಗುರುತುಗಳಿಗೆ " +"ಸೇರಿಸಿಕೊಳ್ಳಿ. ಈಗ \n" +" ನೀವು ಸಣ್ಣಪುಟಗುರುತುಗಳನ್ನು " +"ತಾಣದ ಯಾವ ಪುಟದಿಂದ ಬೇಕಾದರೂ " +"ಆಯ್ಕೆಮಾಡಿಕೊಳ್ಳಬಹುದು.\n" +"ನೆನಪಿರಲಿ, ಕೆಲವು " +"ಸಣ್ಣಪುಟಗುರುತುಗಳಿಗೆ ನೀವು ಈ " +"ತಾಣವನ್ನು ಆಂತರಿಕ (\"internal\") ಎಂದು " +"ಸೂಚಿತವಾಗಿರುವ ಗಣಕದಿಂದ " +"ವೀಕ್ಷಿಸಬೇಕಾಗುತ್ತದೆ.\n" +"( ನಿಮ್ಮ ಗಣಕವು \"internal\" ಹೌದೇ " +"ಅಲ್ಲವೇ ಎಂದು " +"ಗೊತ್ತಿಲ್ಲದಿದ್ದರೆ , ಗಣಕದ " +"ಆಡಳಿತಗಾರರನ್ನು ಕೇಳಿರಿ).

                                                                            \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "ಈ ಪುಟದ ಬಗೆಗಿನ ಮಾಹಿತಿ" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"ನಿಮ್ಮನ್ನು ಯಾವುದೇ ಪುಟದಿಂದ ಆ " +"ಪುಟವನ್ನು ಸೃಷ್ಟಿಸುವ ನೋಟದ " +"ಮಾಹಿತಿಪುಟಕ್ಕೆ ಕೊಂಡೊಯ್ಯುವದು" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "ವಸ್ತುವಿನ ಐಡಿ ತೋರಿಸಿ" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"ಏಕೈಕ ವಸ್ತುವನ್ನು " +"ಪ್ರತಿನಿಧಿಸುವ ಪುಟಗಳ ವಿಶಿಷ್ಠ " +"ಐಡಿ ಮತ್ತು ಒಳವಿಷಯಬಗೆಯನ್ನು " +"ತೋರಿಸುತ್ತದೆ." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "" +"ಈ ವಸ್ತುವನ್ನು ಬದಲಿಸಿ(ಈಗಿನ " +"ಕಿಟಕಿಯಲ್ಲಿ)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"ಏಕೈಕ ವಸ್ತುವನ್ನು " +"ಪ್ರತಿನಿಧಿಸುವ ಪುಟಗಳಿಗಾಗಿ " +"ಆಡಳಿತಪುಟಕ್ಕೆ ಒಯ್ಯುತ್ತದೆ" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "" +"ಈ ವಸ್ತುವನ್ನು ಬದಲಿಸಿ(ಹೊಸ " +"ಕಿಟಕಿಯಲ್ಲಿ)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "" +"ಮೇಲಿನಂತೆ, ಆದರೆ " +"ಆಡಳಿತಪುಟವನ್ನು ಹೊಸ " +"ಕಿಟಕಿಯಲ್ಲಿ ತೆರೆಯುವದು." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "ದಿನಾಂಕ:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "ಸಮಯ:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "ಈಗ:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "ಬದಲಿಸಿ / ಬದಲಾವಣೆ :" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "ಪುನರ್ನಿರ್ದೇಶನ ಇಲ್ಲಿಂದ->" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: " +"'/events/search/'." +msgstr "" +"ಇದು ಡೊಮೈನ್ ಹೊರತುಪಡಿಸಿದ " +"ಸಂಪೂರ್ಣ ಪಥವಾಗಿರಬೇಕು " +"ಉದಾ.'/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "ಪುನರ್ನಿರ್ದೇಶನ ಇಲ್ಲಿಗೆ->" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"ಇದು ಮೇಲಿನಂತೆ ಸಂಪೂರ್ಣ " +"ಪಥವಾದರೂ ಆಗಿರಬಹುದು ಅಥವಾ " +"'http://'ದಿಂದ ಆರಂಭವಾಗುವ ಸಂಪೂರ್ಣ URL " +"ಆಗಿರಬಹುದು." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "ಪುನರ್ನಿರ್ದೇಶನ" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "ಪುನರ್ನಿರ್ದೇಶನ(ಗಳು)" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"ಉದಾ:'/about/contact/'. ಮೊದಲು ಮತ್ತು " +"ಕೊನೆಯಲ್ಲಿ ಓರೆಗೆರೆ (/) ಇರುವಂತೆ " +"ನೋಡಿಕೊಳ್ಳಿ." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "ಶೀರ್ಷಿಕೆ" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "ಒಳವಿಷಯ" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "" +"ಟಿಪ್ಪಣಿಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "ಟೆಂಪ್ಲೇಟಿನ ಹೆಸರು" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"ಉದಾ:'flatpages/contact_page.html'. ಇದನ್ನು " +"ಕೊಡದಿದ್ದರೆ ಗಣಕವ್ಯವಸ್ಥೆಯು " +"'flatpages/default.html' ಅನ್ನು ಬಳಸುವದು." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "ನೋಂದಾವಣೆ ಅಗತ್ಯವಿದೆ." + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"ಇದರಲ್ಲಿ ಗುರುತು ಮಾಡಿದರೆ, " +"ಒಳಬಂದ (ಲಾಗಿನ್ ಆದ) ಬಳಕೆದಾರರು " +"ಮಾತ್ರ ಪುಟವನ್ನು ನೋಡಬಹುದು." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "ಚಪ್ಪಟೆ ಪುಟ" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "ಚಪ್ಪಟೆ ಪುಟಗಳು" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "ಹೊರಬರಲಾಗಿದೆ" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "ಹೆಸರು" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "ಸಾಂಕೇತಿಕ ಹೆಸರು" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "ಅನುಮತಿ" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "ಅನುಮತಿಗಳು" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "ಗುಂಪು" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "ಗುಂಪುಗಳು" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "ಬಳಕೆದಾರ-ಹೆಸರು" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"೩೦ ಅಥವಾ ಕಡಿಮೆ ಅಕ್ಷರಗಳು " +"ಅವಶ್ಯ. ( ಅಕ್ಷರಗಳು , ಅಂಕೆಗಳು " +"ಮತ್ತು ಅಂಡರ್ಸ್ಕೋರ್(_) ಗಳು " +"ಮಾತ್ರ)" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "ಮೊದಲ ಹೆಸರು" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "ಕೊನೆಯ ಹೆಸರು" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "ವಿ-ಅಂಚೆ ವಿಳಾಸ" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "ಪ್ರವೇಶಪದ" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr " '[algo]$[salt]$[hexdigest]' ಬಳಸಿ" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "ಸಿಬ್ಬಂದಿ ಸ್ಥಿತಿ" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "" +"ಬಲಕೆದಾರರು ಈ ಆಡಳಿತ ತಾಣಕ್ಕೆ " +"ಪ್ರವೇಶಪಡೆಯಬಹುದೇ ಎಂಬುದನ್ನು " +"ತಿಳಿಸುತ್ತದೆ." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "ಸಕ್ರಿಯ" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"ಈ ಬಳಕೆದಾರರು ಜಾಂಗೋ " +"ಆಡಳಿತಪುಟಕ್ಕೆ " +"ಪ್ರವೇಶಪಡೆಯಬಹುದೇ ಎಂಬುದನ್ನು " +"ತಿಳಿಸುತ್ತದೆ. ಖಾತೆಗಳನ್ನು " +"ಕಿತ್ತುಹಾಕುವ ಬದಲು ಇದನ್ನು " +"ಆಯ್ಕೆಯನ್ನು ತೆಗೆದುಹಾಕಿರಿ." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "ಮಹಾಬಳಕೆದಾರನ ಸ್ಧಿತಿ" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"ಈ ಸದಸ್ಯರು ಸುವ್ಯಕ್ತವಾಗಿ " +"ನೀಡದಿದ್ದರೂ ಎಲ್ಲಾ " +"ಅನುಮತಿಗಳನ್ನು ಪಡೆದಿರುವರು " +"ಎಂಬುದನ್ನು ಸೂಚಿಸುತ್ತದೆ." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "ಕಡೇ ಸಾರಿ ಒಳಬಂದದ್ದು" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "ಸೇರಿದ ದಿನಾಂಕ" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"ವೈಯಕ್ತಿಕವಾಗಿ ನೀಡಲಾಗಿರುವ " +"ಅನುಮತಿಗಳ ಜೊತೆಗೆ, ಈ ಸದಸ್ಯರು " +"ತಮ್ಮ ಗುಂಪಿಗೆ ನೀಡಲಾಗಿರುವ " +"ಅನುಮತಿಗಳನ್ನೂ ಕೂಡ " +"ಪಡೆಯುತ್ತಾರೆ." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "ಬಳಕೆದಾರ ಅನುಮತಿಗಳು" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "ಬಳಕೆದಾರ" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "ಬಳಕೆದಾರರು" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "ವೈಯುಕ್ತಿಕ ಮಾಹಿತಿ" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "ಅನುಮತಿಗಳು" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "ಮಹತ್ವದ ದಿನಾಂಕಗಳು" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "ಗುಂಪುಗಳು" + +#: contrib/auth/models.py:256 +msgid "message" +msgstr "ಸಂದೇಶ" + +#: contrib/auth/forms.py:52 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"ನಿಮ್ಮ ಜಾಲವೀಕ್ಷಕದಲ್ಲಿ " +"ಕುಕೀಗಳು " +"ಸಕ್ರಿಯಗೊಳಿಸಲ್ಪಟ್ಟಂತಿಲ್ಲ. " +"ಒಳಬರಲು ಕುಕೀಗಳು ಅಗತ್ಯ. " + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "ಈ ಖಾತೆಯು ನಿಷ್ಕ್ರಿಯವಾಗಿದೆ" + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "" +"ಪೈಥಾನ್ ಮಾಡೆಲ್ ಕ್ಲಾಸಿನ ಹೆಸರು" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "ಒಳವಿಷಯದ ಬಗೆ" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "ಒಳವಿಷಯದ ಬಗೆಗಳು" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "ಅಧಿವೇಶನದ ಕೀಲಿಕೈ" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "ಅಧಿವೇಶನದ ದತ್ತಾಂಶ" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "ಅವಧಿಮೀರುವ ದಿನಾಂಕ" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "ಅಧಿವೇಶನ" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "ಅಧಿವೇಶನಗಳು" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "ಡೊಮೈನ್ ಹೆಸರು" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "ತೋರಿಸುವ ಹೆಸರು" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "ತಾಣ" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "ತಾಣಗಳು" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "ಸೋಮವಾರ" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "ಮಂಗಳವಾರ" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "ಬುಧವಾರ" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "ಗುರುವಾರ" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "ಶುಕ್ರವಾರ" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "ಶನಿವಾರ" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "ರವಿವಾರ" + +#: utils/dates.py:14 +msgid "January" +msgstr "ಜನವರಿ" + +#: utils/dates.py:14 +msgid "February" +msgstr "ಫೆಬ್ರುವರಿ" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "ಮಾರ್ಚ್" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "ಎಪ್ರಿಲ್" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "ಮೇ" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "ಜೂನ್" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "ಜುಲೈ" + +#: utils/dates.py:15 +msgid "August" +msgstr "ಆಗಸ್ಟ್" + +#: utils/dates.py:15 +msgid "September" +msgstr "ಸೆಪ್ಟೆಂಬರ್" + +#: utils/dates.py:15 +msgid "October" +msgstr "ಅಕ್ಟೋಬರ್" + +#: utils/dates.py:15 +msgid "November" +msgstr "ನವೆಂಬರ್" + +#: utils/dates.py:16 +msgid "December" +msgstr "ಡಿಸೆಂಬರ್" + +#: utils/dates.py:19 +msgid "jan" +msgstr "ಜನವರಿ" + +#: utils/dates.py:19 +msgid "feb" +msgstr "ಫೆಬ್ರವರಿ" + +#: utils/dates.py:19 +msgid "mar" +msgstr "ಮಾರ್ಚ್" + +#: utils/dates.py:19 +msgid "apr" +msgstr "ಏಪ್ರಿಲ್" + +#: utils/dates.py:19 +msgid "may" +msgstr "ಮೇ" + +#: utils/dates.py:19 +msgid "jun" +msgstr "ಜೂನ್" + +#: utils/dates.py:20 +msgid "jul" +msgstr "ಜುಲೈ" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ಆಗಸ್ಟ್" + +#: utils/dates.py:20 +msgid "sep" +msgstr "ಸೆಪ್ಟೆಂಬರ್" + +#: utils/dates.py:20 +msgid "oct" +msgstr "ಅಕ್ಟೋಬರ್" + +#: utils/dates.py:20 +msgid "nov" +msgstr "ನವೆಂಬರ್" + +#: utils/dates.py:20 +msgid "dec" +msgstr "ಡಿಸೆಂಬರ್" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "ಜನವರಿ." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "ಫೆಬ್ರವರಿ." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "ಆಗಸ್ಟ್." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "ಸೆಪ್ಟೆಂಬರ್." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "ಅಕ್ಟೋಬರ್." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "ನವೆಂಬರ್." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "ಡಿಸೆಂಬರ್." + +#: utils/timesince.py:12 +msgid "year" +msgstr "ವರ್ಷ" + +#: utils/timesince.py:13 +msgid "month" +msgstr "ತಿಂಗಳು" + +#: utils/timesince.py:14 +msgid "week" +msgstr "ವಾರ" + +#: utils/timesince.py:15 +msgid "day" +msgstr "ದಿನ" + +#: utils/timesince.py:16 +msgid "hour" +msgstr "ಘಂಟೆ" + +#: utils/timesince.py:17 +msgid "minute" +msgstr "ನಿಮಿಷ" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "F j" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "ಅರೇಬಿಕ್" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "ಬೆಂಗಾಲಿ" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "ಝೆಕ್" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "ವೆಲ್ಷ್" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "ಡ್ಯಾನಿಷ್" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "ಜರ್ಮನ್" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "ಗ್ರೀಕ್" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "ಇಂಗ್ಲಿಷ್" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "ಸ್ಪ್ಯಾನಿಷ್" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "ಅರ್ಜೆಂಟೈನಾದ ಸ್ಪ್ಯಾನಿಷ್" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "ಫಿನ್ನಿಶ್" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "ಫ್ರೆಂಚ್" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "ಗ್ಯಾಲಿಶಿಯನ್" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "ಹಂಗೇರಿಯನ್" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "ಹೀಬ್ರೂ" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "ಐಸ್‍ಲ್ಯಾಂಡಿಕ್" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "ಇಟಾಲಿಯನ್" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "ಜಪಾನೀಸ್" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "ಡಚ್" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "ನಾರ್ವೇಜಿಯನ್" + +#: conf/global_settings.py:59 +msgid "Brazilian" +msgstr "ಬ್ರಾಝಿಲಿಯನ್" + +#: conf/global_settings.py:60 +msgid "Romanian" +msgstr "ರೋಮೇನಿಯನ್" + +#: conf/global_settings.py:61 +msgid "Russian" +msgstr "ರಶಿಯನ್" + +#: conf/global_settings.py:62 +msgid "Slovak" +msgstr "ಸ್ಲೋವಾಕ್" + +#: conf/global_settings.py:63 +msgid "Slovenian" +msgstr "ಸ್ಲೋವೇನಿಯನ್" + +#: conf/global_settings.py:64 +msgid "Serbian" +msgstr "ಸೆರ್ಬಿಯನ್" + +#: conf/global_settings.py:65 +msgid "Swedish" +msgstr "ಸ್ವೀಡಿಷ್" + +#: conf/global_settings.py:66 +msgid "Tamil" +msgstr "ತಮಿಳು" + +#: conf/global_settings.py:67 +msgid "Turkish" +msgstr "ಟರ್ಕಿಶ್" + +#: conf/global_settings.py:68 +msgid "Ukrainian" +msgstr "ಯುಕ್ರೇನಿಯನ್" + +#: conf/global_settings.py:69 +msgid "Simplified Chinese" +msgstr "ಸರಳೀಕೃತ ಚೈನೀಸ್" + +#: conf/global_settings.py:70 +msgid "Traditional Chinese" +msgstr "ಸಂಪ್ರದಾಯಿಕ ಚೈನೀಸ್ " + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "" +"ಈ ಬೆಲೆಯು ಕೇವಲ ಅಕ್ಷರಗಳು , " +"ಅಂಕೆಗಳು ಮತ್ತು ಅಂಡರ್ಸ್ಕೋರ್(_) " +"ಗಳನ್ನು ಒಳಗೊಂಡಿರಬೇಕು" + +#: core/validators.py:67 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"ಈ ಬೆಲೆಯು ಅಕ್ಷರಗಳು , ಅಂಕೆಗಳು , " +"ಅಡಿಗೆರೆ ಅರ್ಥಾತ್ " +"ಅಂಡರ್ಸ್ಕೋರ್(_) ,ಅಡ್ಡಗೆರೆ " +"ಅರ್ಥಾತ್ ಡ್ಯಾಷ್(-) ಮತ್ತು " +"ಓರೆಗೆರೆ ಅರ್ಥಾತ್ ಸ್ಲ್ಯಾಶ್ (/) " +"ಗಳನ್ನು ಒಳಗೊಳ್ಳಬಹುದು." + +#: core/validators.py:71 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"ಈ ಬೆಲೆಯು ಕೇವಲ ಅಕ್ಷರಗಳು , " +"ಅಂಕೆಗಳು ಮತ್ತು ಅಂಡರ್ಸ್ಕೋರ್(_) " +"ಮತ್ತು ಹೈಫನ್(-)ಗಳನ್ನು " +"ಒಳಗೊಂಡಿರಬೇಕು" + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "" +"ಇಂಗ್ಲೀಷ್ ದೊಡ್ಡಕ್ಷರಗಳನ್ನು " +"ಇಲ್ಲಿ ಬಳಸುವಂತಿಲ್ಲ" + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "" +"ಇಂಗ್ಲೀಷ್ ಸಣ್ಣಕ್ಷರಗಳನ್ನು " +"ಇಲ್ಲಿ ಬಳಸುವಂತಿಲ್ಲ" + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "" +"ಅಲ್ಪವಿರಾಮ(,)ಗಳಿಂದ ಬೇರ್ಪಟ್ಟ " +"ಅಂಕೆಗಳನ್ನು ಮಾತ್ರ ಬರೆಯಿರಿ." + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "" +"ಇ-ಅಂಚೆಗಳ ವಿಳಾಸಗಳನ್ನು, " +"ಅಲ್ಪವಿರಾಮ(,)ಗಳಿಂದ " +"ಬೇರ್ಪಟ್ಟಿರುವಂತೆ ಸೂಚಿಸಿರಿ." + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "ಸರಿಯಾದ IP ವಿಳಾಸ ಬರೆಯಿರಿ" + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "ಇಲ್ಲಿ ಖಾಲಿ ಬಿಡುವಂತಿಲ್ಲ" + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "" +"ಅಂಕೆಗಳಲ್ಲದೆ ಯಾವದೇ " +"ಅಕ್ಷರಗಳನ್ನು ಇಲ್ಲಿ " +"ಬಳಸುವಂತಿಲ್ಲ" + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "" +"ಈ ಬೆಲೆಯಲ್ಲಿ ಅಂಕೆಗಳಷ್ಟೇ " +"ಇರುವಂತಿಲ್ಲ" + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "ಪೂರ್ಣಾಂಕವೊಂದನ್ನು ಬರೆಯಿರಿ" + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "" +"ವರ್ಣಮಾಲೆಯ ಅಕ್ಷರಗಳನ್ನು " +"ಮಾತ್ರ ಇಲ್ಲಿ ಬಳಸಬಹುದು." + +#: core/validators.py:138 +msgid "Year must be 1900 or later." +msgstr "" +"ವರ್ಷವು ೧೯೦೦ ಅಥವಾ " +"ನಂತರದ್ದಿರಬೇಕು." + +#: core/validators.py:142 +#, python-format +msgid "Invalid date: %s." +msgstr "ತಪ್ಪು ದಿನಾಂಕ: %s." + +#: core/validators.py:146 db/models/fields/__init__.py:415 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "" +"ಸರಿಯಾದ ದಿನಾಂಕವನ್ನು " +"ವವವವ-ತಿತಿ-ದಿದಿ ರೀತಿಗೆ " +"ಹೊಂದುವಂತೆ ಸೂಚಿಸಿರಿ." + +#: core/validators.py:151 +msgid "Enter a valid time in HH:MM format." +msgstr "" +"HH:MM ರೂಪದಲ್ಲಿ ಸರಿಯಾದ ಸಮಯವನ್ನು " +"ಬರೆಯಿರಿ" + +#: core/validators.py:155 db/models/fields/__init__.py:477 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "" +"ಸರಿಯಾದ ದಿನಾಂಕ/ವೇಳೆಯನ್ನು " +"ವವವವ-ತಿತಿ-ದಿದಿ ಗಗ:ನಿನಿ " +"ರೀತಿಗೆ ಹೊಂದುವಂತೆ ಸೂಚಿಸಿರಿ." + +#: core/validators.py:160 +msgid "Enter a valid e-mail address." +msgstr "" +"ಕ್ರಮಬದ್ಧ ವಿ-ವಿಳಾಸವೊಂದನ್ನು " +"ಇಲ್ಲಿ ಬರೆಯಿರಿ" + +#: core/validators.py:172 core/validators.py:401 forms/__init__.py:661 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" +"ಯಾವದೇ ಕಡತವನ್ನೂ " +"ಸಲ್ಲಿಸಲಾಗಿಲ್ಲ.ನಮೂನೆಯ ಮೇಲಿನ " +"ಸಂಕೇತೀಕರಣ ಬಗೆಯನ್ನು " +"ಪರೀಕ್ಷಿಸಿ." + +#: core/validators.py:176 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"ಸರಿಯಾದ ಚಿತ್ರವನ್ನು ಏರಿಸಿ. " +"ನೀವು ಏರಿಸಿದ ಕಡತವು ಚಿತ್ರವಲ್ಲ " +"ಅಥವಾ ಅದು ಕೆಟ್ಟು ಹೋಗಿರುವ " +"ಚಿತ್ರ. " + +#: core/validators.py:183 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "" +"%s URL ಸರಿಯಾದ ಚಿತ್ರದೆಡೆಗೆ " +"ಒಯ್ಯುತ್ತಿಲ್ಲ." + +#: core/validators.py:187 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"ದೂರವಾಣಿ ಅಂಕೆಗಳು XXX-XXX-XXXX " +"ರೀತಿಯಲ್ಲಿರಬೇಕು. \"%s\" " +"ತಪ್ಪಾಗಿರುತ್ತದೆ." + +#: core/validators.py:195 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "" +"%s URL ಸರಿಯಾದ ಕ್ವಿಕ್‍ಟೈಮ್ " +"ದೃಶ್ಯದೆಡೆಗೆ ಒಯ್ಯುತ್ತಿಲ್ಲ." + +#: core/validators.py:199 +msgid "A valid URL is required." +msgstr "ಸರಿಯಾದ URL ಅವಶ್ಯವಿದೆ" + +#: core/validators.py:213 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"ಸರಿಯಾದ HTML ಅವಶ್ಯ. ಇಲ್ಲಿನ " +"ನಿರ್ದಿಷ್ಟ ತಪ್ಪುಗಳು:\n" +"%s" + +#: core/validators.py:220 +#, python-format +msgid "Badly formed XML: %s" +msgstr "ತಪ್ಪು XML: %s" + +#: core/validators.py:230 +#, python-format +msgid "Invalid URL: %s" +msgstr "ತಪ್ಪು URL: %s" + +#: core/validators.py:234 core/validators.py:236 +#, python-format +msgid "The URL %s is a broken link." +msgstr "%s ಈ URL ಮುರಿದ ಕೊಂಡಿಯಾಗಿದೆ." + +#: core/validators.py:242 +msgid "Enter a valid U.S. state abbreviation." +msgstr "" +" ಅಮೇರಿಕೆಯ ಸಂಯುಕ್ತ ಸಂಸ್ಥಾನದ " +"ರಾಜ್ಯದ ಸರಿಯಾದ " +"ಸಂಕ್ಷಿಪ್ತರೂಪವನ್ನು ಕೊಡಿ." + +#: core/validators.py:256 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgstr "" +"ನಾಲಿಗೆ ಬಿಗಿ ಹಿಡಿಯಿರಿ! %s " +"ಶಬ್ದವನ್ನು ಇಲ್ಲಿ " +"ಬಳಸುವಂತಿಲ್ಲ." +"ನಾಲಿಗೆ ಬಿಗಿ ಹಿಡಿಯಿರಿ! %s ಈ " +"ಶಬ್ದಗಳನ್ನು ಇಲ್ಲಿ " +"ಬಳಸುವಂತಿಲ್ಲ." + +#: core/validators.py:263 +#, python-format +msgid "This field must match the '%s' field." +msgstr "" +"ಈ ಅಂಶವು '%s' ಅಂಶದೊಂದಿಗೆ " +"ಹೊಂದಿಕೊಳ್ಳಬೇಕು." + +#: core/validators.py:282 +msgid "Please enter something for at least one field." +msgstr "" +"ಕನಿಷ್ಠ ಒಂದು ಅಂಶದಲ್ಲಿ " +"ಏನನ್ನಾದರೂ ಬರೆಯಿರಿ." + +#: core/validators.py:291 core/validators.py:302 +msgid "Please enter both fields or leave them both empty." +msgstr "" +"ಎರಡೂ ಅಂಶಗಳನ್ನು ಭರತಿ ಮಾಡಿರಿ " +"ಅಥವಾ ಎರಡನ್ನೂ ಖಾಲಿಯಾಗಿಡಿ." + +#: core/validators.py:309 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "" +" %(field)s ಇದು %(value)s ಆಗಿದ್ದರೆ ಈ " +"ಅಂಶವನ್ನು ಕೊಡಲೇಬೇಕು" + +#: core/validators.py:321 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "" +" %(field)s ಇದು %(value)s ಆಗಿಲ್ಲದಿದ್ದರೆ ಈ " +"ಅಂಶವನ್ನು ಕೊಡಲೇಬೇಕು" + +#: core/validators.py:340 +msgid "Duplicate values are not allowed." +msgstr "" +"ಪಡಿಯಚ್ಚುಬೆಲೆಗಳಿಗೆ " +"ಅನುಮತಿಯಿಲ್ಲ" + +#: core/validators.py:363 +#, python-format +msgid "This value must be a power of %s." +msgstr "ಈ ಬೆಲೆಯು %sದ ಘಾತವಾಗಿರಬೇಕು." + +#: core/validators.py:374 +msgid "Please enter a valid decimal number." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆ ಬರೆಯಿರಿ" + +#: core/validators.py:378 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +"Please enter a valid decimal number with at most %s total digits." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ಹೆಚ್ಚೆಂದರೆ " +"ಒಟ್ಟು %s ಅಂಕೆ ಇರುವಂತೆ - " +"ಬರೆಯಿರಿ." +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ಹೆಚ್ಚೆಂದರೆ " +"ಒಟ್ಟು %s ಅಂಕೆಗಳು ಇರುವಂತೆ - " +"ಬರೆಯಿರಿ." + +#: core/validators.py:381 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ಪೂರ್ಣಾಂಕ ಭಾಗವು " +"ಹೆಚ್ಚೆಂದರೆ ಒಟ್ಟು %s ಅಂಕೆ " +"ಇರುವಂತೆ - ಬರೆಯಿರಿ." +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ಪೂರ್ಣಾಂಕ ಭಾಗವು " +"ಹೆಚ್ಚೆಂದರೆ ಒಟ್ಟು %s ಅಂಕೆಗಳು " +"ಇರುವಂತೆ - ಬರೆಯಿರಿ." + +#: core/validators.py:384 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +"Please enter a valid decimal number with at most %s decimal places." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ದಶಮಾಂಶ ಭಾಗವು " +"ಹೆಚ್ಚೆಂದರೆ ಒಟ್ಟು %s ಸ್ಥಾನ " +"ಇರುವಂತೆ - ಬರೆಯಿರಿ." +"ದಯವಿಟ್ಟು ಸರಿಯಾದ ದಶಮಾನ " +"ಸಂಖ್ಯೆಯನ್ನು - ದಶಮಾಂಶ ಭಾಗವು " +"ಹೆಚ್ಚೆಂದರೆ %s ಸ್ಥಾನಗಳು " +"ಇರುವಂತೆ - ಬರೆಯಿರಿ." + +#: core/validators.py:394 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"ನೀವು ಏರಿಸಿದ ಕಡತವು ಕನಿಷ್ಟ %s " +"ಬೈಟ್‍ಗಳಷ್ಟು ದೊಡ್ಡದಿದೆ ಎಂದು " +"ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ." + +#: core/validators.py:395 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"ನೀವು ಏರಿಸಿದ ಕಡತವು %s " +"ಬೈಟ್‍ಗಳಿಗಿಂತ ಹೆಚ್ಚಿರದ ಹಾಗೆ " +"ನೋಡಿಕೊಳ್ಳಿ." + +#: core/validators.py:412 +msgid "The format for this field is wrong." +msgstr "ಈ ಅಂಶದ ಸ್ವರೂಪವು ತಪ್ಪಾಗಿದೆ" + +#: core/validators.py:427 +msgid "This field is invalid." +msgstr "ಈ ಅಂಶವು ತಪ್ಪಾಗಿದೆ" + +#: core/validators.py:463 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "" +" %s ದಿಂದ ಏನನ್ನೂ " +"ಹೊರತೆಗೆದುಕೊಳ್ಳಲಾಗಲಿಲ್ಲ." + +#: core/validators.py:466 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +" %(url)s ಈ URL ತಪ್ಪು ಒಳವಿಷಯ-ಬಗೆಯ " +"ಶಿರೋ‍ಭಾಗ '%(contenttype)s' ಅನ್ನು " +"ಮರಳಿಸಿತು." + +#: core/validators.py:499 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"ದಯವಿಟ್ಟು %(line)s - ಸಾಲಿನಿಂದ " +"ಮುಚ್ಚಿರದ %(tag)s ಟ್ಯಾಗ್ ಅನ್ನು " +"ಮುಚ್ಚಿರಿ . (ಸಾಲು \"%(start)s\" ಎಂದು " +"ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: core/validators.py:503 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +" %(line)s ಸಾಲಿನಲ್ಲಿ ಆರಂಭವಾಗುವ ಕೆಲ " +"ಪಠ್ಯಭಾಗವನ್ನು ಆ ಸಂದರ್ಭದಲ್ಲಿ " +"ಬಳಸಲಾಗದು.(ಸಾಲು \"%(start)s\" ಎಂದು " +"ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: core/validators.py:508 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with " +"\"%(start)s\".)" +msgstr "" +" %(line)sನೆ ಸಾಲಿನಲ್ಲಿರುವ \"%(attr)s\" " +"ತಪ್ಪು ಗುಣಧರ್ಮವಾಗಿದೆ . (ಸಾಲು " +"\"%(start)s\" ಎಂದು ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: core/validators.py:513 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with " +"\"%(start)s\".)" +msgstr "" +" %(line)s ನೆ ಸಾಲಿನಲ್ಲಿರುವ \"<%(tag)s>\" " +"ತಪ್ಪು ಟ್ಯಾಗ್ ಆಗಿದೆ . (ಸಾಲು " +"\"%(start)s\" ಎಂದು ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: core/validators.py:517 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +" %(line)s ನೆ ಸಾಲಿನಲ್ಲಿರುವ ಟ್ಯಾಗ್ " +"ಗೆ ಅಗತ್ಯವಾದ ಒಂದು ಅಥವಾ ಹೆಚ್ಚು " +"ಗುಣಧರ್ಮಗಳು ಕಾಣೆಯಾಗಿವೆ . " +"(ಸಾಲು \"%(start)s\" ಎಂದು " +"ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: core/validators.py:522 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +" %(line)s ನೆ ಸಾಲಿನಲ್ಲಿರುವ \"%(attr)s\" ಗೆ " +"ತಪ್ಪು ಬೆಲೆ ಇದೆ. (ಸಾಲು \"%(start)s\" " +"ಎಂದು ಆರಂಭವಾಗುತ್ತದೆ.)" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "" +" %(verbose_name)s ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ " +"ನಿರ್ಮಿಸಲಾಯಿತು." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "" +" %(verbose_name)s ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ " +"ಮಾರ್ಪಡಿಸಲಾಯಿತು." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "" +" %(verbose_name)s ಅನ್ನು " +"ತೆಗೆದುಹಾಕಲಾಯಿತು" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" +"ಕೊಟ್ಟಿರುವ %(field)sಗೆ ಈ %(type)s " +"ಹೊಂದಿರುವ %(object)s ಈಗಾಗಲೇ ಇದೆ." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" +"ಈ %(fieldname)s ಹೊಂದಿರುವ %(optname)s ಈಗಾಗಲೇ " +"ಇದೆ." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "ಈ ಅಂಶ ಅಗತ್ಯ." + +#: db/models/fields/__init__.py:340 +msgid "This value must be an integer." +msgstr "" +"ಈ ಬೆಲೆಯು ಪೂರ್ಣ ಸಂಖ್ಯೆ ಇರಬೇಕು." + +#: db/models/fields/__init__.py:372 +msgid "This value must be either True or False." +msgstr "" +"ಈ ಬೆಲೆಯು ಹೌದು ಅಥವಾ ಇಲ್ಲ " +"ಇರಬೇಕು." + +#: db/models/fields/__init__.py:388 +msgid "This field cannot be null." +msgstr "" +"ಈ ಅಂಶವನ್ನು ಖಾಲಿ ಬಿಡುವಂತಿಲ್ಲ." + +#: db/models/fields/__init__.py:571 +msgid "Enter a valid filename." +msgstr "" +"ಸರಿಯಾದ ಕಡತದ ಹೆಸರನ್ನು " +"ಬರೆಯಿರಿ" + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "ಸರಿಯಾದ %s ಅನ್ನು ಬರೆಯಿರಿ ." + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "" +"ಅನೇಕ ಐಡಿಗಳನ್ನು " +"ಅಲ್ಪವಿರಾಮ(,)ಗಳಿಂದ " +"ಬೇರ್ಪಡಿಸಿರಿ" + +#: db/models/fields/related.py:620 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"ಒಂದಕ್ಕಿಂತ ಹೆಚ್ಚನ್ನು " +"ಆಯ್ದುಕೊಳ್ಳಲು ಮ್ಯಾಕ್ ಗಣಕದ " +"ಮೇಲೆ \"ಕಂಟ್ರೋಲ್\", ಅಥವಾ " +"\"ಕಮ್ಯಾಂಡ್\" ಅನ್ನು ಒತ್ತಿ " +"ಹಿಡಿಯಿರಿ." + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr "" +"ದಯವಿಟ್ಟು ಸರಿಯಾದ %(self)s " +"ಐಡಿಗಳನ್ನು ಸೂಚಿಸಿರಿ . ಈಗ " +"ಸೂಚಿಸಿದ %(value)r ತಪ್ಪಾಗಿದೆ." +"ದಯವಿಟ್ಟು ಸರಿಯಾದ %(self)s " +"ಐಡಿಗಳನ್ನು ಸೂಚಿಸಿರಿ . ಈಗ " +"ಸೂಚಿಸಿದ %(value)s ತಪ್ಪಾಗಿವೆ" + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgstr "" +"ನಿಮ್ಮ ಗದ್ಯ %s ಅಕ್ಷರಕ್ಕಿಂತ " +"ಕಡಿಮೆ ಇರುವಂತೆ ಜಾಗ್ರತೆ ವಹಿಸಿ." +"ನಿಮ್ಮ ಗದ್ಯ %s ಅಕ್ಷರಗಳಿಗಿಂತ " +"ಕಡಿಮೆ ಇರುವಂತೆ ಜಾಗ್ರತೆ ವಹಿಸಿ." + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "" +" ಇಲ್ಲಿ " +"ಸಾಲುಗಳನ್ನುತುಂಡರಿಸುವಂತಿಲ್ಲ" + +#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" +"ಸರಿಯಾದ ಆಯ್ಕೆಯನ್ನು " +"ಆಯ್ದುಕೊಳ್ಳಿರಿ.'%(data)s' %(choices)s " +"ನಲ್ಲಿಲ್ಲ." + +#: forms/__init__.py:663 +msgid "The submitted file is empty." +msgstr "ಸಲ್ಲಿಸಲಾದ ಕಡತ ಖಾಲಿ ಇದೆ." + +#: forms/__init__.py:719 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "" +"-೩೨,೭೬೮ ಹಾಗೂ ೩೨,೭೬೭ ರ ನಡುವಿನ " +"ಪೂರ್ಣ ಸಂಖ್ಯೆ ಕೊಡಿ." + +#: forms/__init__.py:729 +msgid "Enter a positive number." +msgstr "ಧನಾತ್ಮಕ ಸಂಖ್ಯೆ ಕೊಡಿ." + +#: forms/__init__.py:739 +msgid "Enter a whole number between 0 and 32,767." +msgstr "" +"೦ ಮತ್ತು ೩೨,೭೬೭ ರ ನಡುವಿನ ಒಂದು " +"ಪೂರ್ಣಾಂಕ ಕೊಡಿ" + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "ಹೌದು,ಇಲ್ಲ,ಇರಬಹುದು" + diff --git a/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/kn/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..cbb9fb5ffcda977b7b04aec48115e7248b6a2539 GIT binary patch literal 2205 zcwU8*PiS049LL9`v1zpaiCPq@j%aB^d2y4N8rN8iNrSD~wq&;!@icim-Mr@Qo8`T? z(TE9naqu@VaJGiIC=sUr^;B#OHco5tJ z_9h$z+rUxq3^>+e{J#Zvfj3%A{8|$4W)klX$iW}L0r0mZ&daUFzpFL52mBB@4T7)W z{1t+Bg5QHrfIlVYU%;2ZKfxYwAA%hP-v#@?3(5ITo3Z~J{2APfq+S4j1)l|5cbh!- zgD>iQz=Pm0=z~+>m*CyqrViC^`u177iF*muE!MR^!qJ_c${(=>F(hwZD$3 z>qmHomQZY+&tv$W#`jd>t7q{@vVT%9N4V%NcC(>;o()Y4#}yN<{9E3b8OQ9@INS~jwL#26AO&h_8v}waKPTq40lOa2)l!xUbHZF^>Vp*Qy zIer%3D9^I2bfq2g8CCYVJ*ga99tyg+@bcVt9eawiY#HStCq>AWWuSRh<~_5^O_qJy zjQWm7R)l3A8l%zQAtMji0X>J6*6pZD~qU+X)Zf&_-v zxSV!^9d@ri2FcrjD3)N>?Re+ZQAF_yWl@lpHf+YYk4NTs=2)}#p6*n~u<}CbA(@#HvJK_O z;j1NAINqBWitqy&zI7%yY7I2)bnJ7|w~l$X$~#^m%?Bo&kj2q*T(87YJ&vkzv>8X& zd0b!SbefB!Z=mEbT94}&;;2Rz>sTR*;^@1$J|9P`c%#~8i99aH(FSd*j2srp2J5Vj zS-(VAHKPD42oXnXq?^;G(RK2{I*UXq_uTu1zFO-e?bNiLQ;;w=`LgC zl+aRQtK&vSx`H8b9lJhfk~D`|GPr08uhJ~%X|h_sP8Tn2i7GULWpf=*lKhYKN>bfU z1*252nKwbHZBTcTH1APEciZ$4ii_sCm{wM4T{P3bO_mGBzh)BtT-VB}UD~8-a4P|$ ztT(A0^Tx0-aE!Wgh?-FEy5BQ1T{pR3HTs3E2e7%dU)Vlln!i4;nQ0}NQkgn, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Django-kn 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-09-25 15:43+0200\n" +"PO-Revision-Date: 2007-01-08 20:22+0530\n" +"Last-Translator: Kannada Localization Team \n" +"Language-Team: Kannada \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "ಲಭ್ಯ %s " + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "ಎಲ್ಲವನ್ನೂ ಆಯ್ದುಕೊಳ್ಳಿ" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "ಸೇರಿಸಿ" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "ತೆಗೆದು ಹಾಕಿ" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s ಆಯ್ದುಕೊಳ್ಳಲಾಗಿದೆ" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "" +"ನಿಮ್ಮ ಆಯ್ಕೆ(ಗಳ)ನ್ನು ಆರಿಸಿ " +"ಮತ್ತು ಕ್ಲಿಕ್ಕಿಸಿ" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"ಜನವರಿ ಫೆಬ್ರುವರಿ ಮಾರ್ಚ್ " +"ಎಪ್ರಿಲ್ ಮೇ ಜೂನ್ ಜುಲೈ ಆಗಸ್ಟ್ " +"ಸೆಪ್ಟೆಂಬರ್ ನವೆಂಬರ್ ಡಿಸೆಂಬರ್" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "" +"ರವಿವಾರ ಸೋಮವಾರ ಮಂಗಳವಾರ " +"ಬುಧವಾರ ಗುರುವಾರ ಶುಕ್ರವಾರ " +"ಶನಿವಾರ" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "ರ ಸೋ ಮ ಬು ಗು ಶು ಶ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "ಈಗ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "ಗಡಿಯಾರ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "ಸಮಯವೊಂದನ್ನು ಆರಿಸಿ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "ಮಧ್ಯರಾತ್ರಿ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "ಬೆಳಗಿನ ೬ ಗಂಟೆ " + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "ಮಧ್ಯಾಹ್ನ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "ರದ್ದುಗೊಳಿಸಿ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "ಈ ದಿನ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "ಪಂಚಾಂಗ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "ನಿನ್ನೆ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "ನಾಳೆ" diff --git a/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..21f9e8aecbca0241caec5f5abe1ec02b60263137 GIT binary patch literal 26751 zcwWtW349!9dG~7?QewhQDA2^2*s&!Mt$fAT`iNyowq@&zCChda$DZ9Ctwy^$v&~=YF1f{k6wm@Ct+9&u})n{}SA0MZt|sq^0txC-!s z%Z#}aaLr}X@8D%J|1jX=0Z%Bb0zL-t*?_+f_-BBR1bii61o(!_?Zo0|F)HX22%^z8LVafUnc{cdnB4=U2&j?$!A|tMhzymF)8et7QG30`36( z4d5k!J1&=Xhc6dA*nm$3^e-2Dy$J9Mz`wa%=KILya;`6E{dWLy6myYf%xb_kOU`u& za2;R~u$|_y1i!ai(*HYve*pM!OtJ>>GQbUh-GEO646cyUJf`?bH7JR*Nwd~^^fYY?^)q;m>u-L-@_pg!n{xw3M5x@&*zBMxbB;WzS z$u)A`cLF{O@E^4Qo-3t(|CK`T@hhd?y;A0RI$$^8G~g)U9a?|!TEW}n*2?;u*9w2- z*UI<o2+0&WHTKHwI>)$4@L$JYse-w2okEUc6B{`oqY_pR&X9PeBwbbbFi zIrm4`$$mcp_$0uu16~FA^L0Ys)osd;ZL*(yn~dMrCiEL=lX1@l>;im^_WxL$tos># z|8kqm_pLU;)4#R}Up=f{>MsL)3Seiu&~1Oa@aKra8`@>v$#%idG~gb0K0dE2PG2nA({L@699st|~xaum||FwY62fP`u2>6IjS+5G% z2RPd)`~Uk+!N(^%W&dC3l=Z&dDd+u7r^wMGa`OJvoX~qsPV`87PR_eKC+9nslXDky zBFD9y$j=$Ae`8MM@3x%m=VLjc+ZS@O{`UZf=sY_9AP%z&@P;nYm$!BaJ>JtL`?#x1 z@b|$kp~H&xa{f!#i##1(FY@)Z{P2mA`)FyNCm ziabwjl=c^G6gs>a@F-vxkdGyB+g*Y5(B0e=RFf952V*#zJ#w#&F*ZkPRBxI^G$c8J_Rd56fyr8{Kar|l5EI=(~j z9s%|Mz7+5z;O78$0&dO=-u%4i)w#Uj{ajw~a%W!n^FD4w;VzNyU+xmTJZ87d zw`RBS(T3eJev86ucFVl|yJfzkyX71wcFXz&ZLjT?bI-zxU4B++q#QuF|pX~1!`@}9?&?Ehx(j)t9?~#4(=#hT=dPLrbdgR=vdu08vNA`Vl zkL>FO+WxX0k*hapzqj>>|MR{cIp3ptML(_Wm38;^ir%`mSL~zTD|maMw!foS`80|Id0wuRozr?Bo`}4w|P={Gn&`iJ$w{KJf>>)u;aZewk;@ezBK__KRJ6{(hO~ zuKi+fzO-NL!^Lm{OMr(Ds6Pb=mN)M|Am@AFfb8?agEIaSz%zgw4@&>r4+@^{J}7wk z&_TiPJzD?ygMz=W0bU3A-Gg%89vHK;fTR8L{+)iI!~Ok&$BPchxGN6HJXZmZ0B#1n z7Vx=R|M^3LkFOn)c`iPz{Bl_Kw+ZlNfV&S1{yueB?BTy1mU;RIL~dTA@Ejmq9rNY^ z!T)CmWFJ2tkonD^;N!7_BJYf-@`+){tZL2US&w={=6aKgEtNdUKWN#&)zd6>wjZN zE*M*Ghcy zz_mh$O-GFR3&7KWG2s12WZfEs1T1H685R28Gb;GFZ&c*!N29`DE5_vfmyJokjxpJP zx5C3?LYJdsa^9ISIp?d!@`(y#rf@X7Y0q9=xqGHFdw z>)&-$L?MT=eSm#wD(LyT1QyT;%bxV=`~=F~P%i z$Ar($9TR@J_n6T0N5@27A8}mvv*);+_a(<=o!bCk4*2QgB2VQL;^%(wgz(c(06#?M zyH4o$>JmMzj^Qm@jqX7gY5VFH;A5n z@<}=0?vsLN=cMq{3r$mLh{{d=cH zj(>Jq`0s(!LXS&qiPN{(g8$QiAfG-cu0$`{{z{ zwZ{~N@2)7yxi=OiUfofY^>!6yA2$?*Pfi!b-cA=~pU+TuOHuIm(xS-8tBOL`Hx`B7 zcNOJ)Uo6VH_Z0MxdL{cB5t&vHq|-&7L%ytpLtc8At~ zq9kVhbj^_Ptx-up@F|>$qn)vhH&g zzS0rA{jDSG+@|y1;fVk6QAhO1KkIzI(0(f?WS>t|c=?3j<7pEz-!&6*o}LL=_t1ol z8=Vk3o}3W6)h2`vf1>bKg>O>$E`@g~{HVguDg1`c`@;#@@BIoNT^72mEem}&m1UoY z%W{tKvg~uREc>i#|FA6Qzqu@Y_R_M@=T?QUD+}J((lNmoWq$E{P=(?Xx}Tu&Ql+<8+w8S%cLL4=2Yx`8}X6o_cc6ES|MeaB(nzPR~m`U zNq)a=ntvAB-)F>z{R+K}&Z zr$%(ijuocqNA_dZh#nywe+cdO7{T}7X&+A^=^)Dg5RLu>&)e|0Mq=&VDgzaYBr*{7M?#c;yaQ5@NPVNF;^bX^Ni^B3w2MFK>uqzPuDr_!1Jeg4x{}hz?Wz} z`7f7fdCrLbd6>TUDI}Trw2@p5`F~(jw#S!Zu01Ox2N;>g?+;LSP|KGoT(9sijpUNP zhvyM1#IGh9=|j0+_kO$feTu&SPa}5yLj+fdPJPS@u`w`p<_6RJdoP|};`xS=JTD2= zn6`aU;g$^lJQ`&i@Xt;2@868rGy2_y=T&&V+tTMNMs&}+@SHQ^H$QHL=#`5BYk+Um zzV{i48^}kv1 zd4m!AK)XRPi14VmARqLFMtr5eHKN1el9+8oPdsliV$aFPcrBjS z;Tb@E2OfmT?90&a7kHkAy3ZN0uV2UWY$Lwb=Z*Ln^!p~BpW%5t`hFDf8Cu_t_W9`z+k=bXLsj*s=V;+HY4wr(?HXMdC zeo#uPTHf;t1%*A6PO*a1f&c~oCd-}RlNbG3&G8~4i|g6-df)?XKwp>DZLJ=o&Pk6+ z8v`B31Nmw|VQIBhbHb2NHsgXa1;;A6VZCb4Vm;RjBgY1Tdob9}g-+cDTW~|S;vm47>TKTh zc8~R)I@UjS@YGoUVBe|zM+XK>FBZ4jXMlJsa=~Gl3LlKns9R4(E@2|4=+p{MKn1sE zS1m8rSCHFLAh_*yLbvP@Z(1{x4wILVOWaA!X;u9)9o-l73%7+Xuxxs#Kp|hMKw}af z2!z&B7iluRelaG5X?yk!Y$j7U}4a#~7QnAsvKSyIS>UNFe)i zHQTLX%~Am3w!G#|U&)PRJ|T|PUKGTuv~(=dqajOZt<|I1Wfr%9H6Wx9#19~Xbswj9 zL2SW#qd`?k*Jzg&MsyatPzao9mk-?9jWhv?^a=cOgM+!=UKU|HTB;)xzoqxVgZaV1 zWyUy7*lBH@aH|ffv)1}#k)>yznS_?mGef4L8Iri%=1ra3wr&UCmn<%B`z-@D{MN2X zt?xmrmE5u$g;qmMp}a}#?etJf9oSiJ#7%asUUl+T+j=P5I9gwXK-d$c{;DlzDKPu}pzKG`GGrTqbdGD5ZLkClT-l5T;2iAR$@`yi+Mo$h z>qO1txmCxJVj!7-zNf9CAC4KgC41H!@Xdk1kL#g1DEi(U^r4RWM^b_8pOCYWUM79+ zlsbik&@fQ+Iq;R3#6&0Cg(@#I?P5tLj?t%w@-O0#^I{D!4rqdyW}!L0;%3yRw|38(^EDz_QD> zzCdCCeT=KR6-W4}5co5MYB%)SBA^2%vkW0(tlGt*Q>RM5;zDGs5l9qV3g{jpy;gSu z302=NH6)``C2m%hrv1z~L6L^Sjzq*mOYgfO!n`$@6wK?0z9WJH543{_1cbB^ZVGPs zAq}hfON75>J7qxU?nosY$aLp2Yrtu zZE)5S-!n8rAZEk)81nHCn{uk9Q1uMVBC6nAvd7_z76_f{1ggCp)TtA=Md}+s$tm85 zOwDK`|E{rnyiSXAFFi45XOa?3wo6>1_C-V zJUS+n)8(bwuGS}QSO*}e+OpM_eGb=Azwe18tAaLz0 z>+nT_i1aRe%+-j7=(QtU-PMuK?88$kMm)C@1b!fvqifmru%&~eW<2Rg%9nVb1vz1z zE(8*hBkHK&V=u}rL}(Ij_~M>8R*U8pT}$rlk_` z6#vO>M1F)u_IUo6CwOREXz7%t3YY**)68%+X>rI!ZW$873($ZEBT@xR{LT}#bY}(Y zXY@%Yj|&|*>uxmL2EPr;SPj4=e4WfNFQW~uXvQsqvwb)=9L7vT6HLsqxr}D(6wXB? zV9Joy#*@Q1yX6*(0tU6;($!fD0a_(81E1z zK%YXzMkP7Qt`~!jhM(Ipa_ zGwK8&rPaq;XqQvl)4pZHhK^n9QA*la12w%^+{uhi`ZGyv2Y-xSL(L>`*q$TK;M2sO z`}COh@jLiqqiMw32!=<&{RO`?3k6(*X9;RcAfL%zkXE)-H42r)efH% z$XeT8vu{FJR*oh+LNvzg42(H9MP>|mgwMr-Qg=L#Nl5Ojr^EB5NjYvyS3c$xBs`CN zOwpAc9@CjDW@2=>0!|BK~L$Z$gyi&P1!ybFiPFTf#2}$$A5^Upry%U&-5ivl-%tK^bHJr zMH-vvKswJjbxX_M5a2xxjsMr2HpExj$#10z(+V)J~du7>f>Jz5kwOW-i^6}bKonj*_*j-kCl+-~q5f@5e zLHh_>E(tDmotR0gjN+$R}Hu2(f3Y%!6yg+_(wc3?! z%KF01)s1w0ratkn%kU&UN+z#u=Y|dzp|$Ot<7;00!H3YkmLCR|FN)%ATN}0|UJv)G zL0wErQKg3Dog|d=OLh{FXbPRUon*IwFl;snqC~M0V=qe3^2GX0BZG{rQ`0uBo)@Z{ zoHI7>I#_3FNJ}fncfuViR%13i5$7JrN3l4CXk~J0&NLBtedthVobo**j_BLQI0hfo z1DB!~tl;3F2qF`gHPb&K38hQy{{#xqAblhI;QELPQ|e92i(lN}`2iYiX5c+y=o9J$ePoxhTZSvuOf z=rS3ZqtFighj=BQmeTKH3Eo(I_O>q5q67JG7il`gVMWLZ zyJGs4hzt>BD0m!6CYve}&6eOsEL=-ASH2ah`4sP}A+m^a9Cw;EL6q7#EK~A?YmPZp z2%ghvjsu^hea#6cq^HyBZ>w?k%g~G^KUGhSR4Hdj$%#w8ZgrW&x61?zoF5bC?m<02 zG!oJpJ7&eEht24&3?A(UZX{8|ffzZuN%}EStTq_a4$>69<`^~Q2(0yJg&kzmRut^| zyp`9u&+EL8A4@a$WkyXYCrv~$YX`uPb`XB-7rr+tTS^ese(Z{(UgwQIKVnt zvRWqxw01+WPb6Oi^2RPo8W?UxQs&QvR7PwE{bl$I>a>_`sE>&WW5;Eq7D&IBnj&IG zTFWHiW@5FCy1ohIC_6O#5o-#*D+0{McdAC>k64d0WofRHKY;7!SjLmdSQFEXbdzR^ zbrTYm%N!GK0L9Cr=-66W)m(R@6q?k{V_(HCvPUAZcqTe9{0S#HclIST{AED?hK9lc zO0XFSU%-Dt6(&n^ubz0hzmbRv)ZE0z{ zfjqmE@<|Jv7;U^oep(JuDyPAGst$~hgZ~Owg-+dQawvh+V5ct?XErH^h7Az?L6PK) zjdhHd7#Lv|)QSk9Pn86S5fQmj)iKo0e?*qkQYghNeiOaGh0x46PKE!3hCK=TL&dPu z^E<&K1;>m8K9MljU&@VR2xiG!Bi%iRyASkP<9$a)`-g{C9^o)6HyD=PQf^;d4s&CE z-dZ^_JeKPT9L7|RFCkc)Hf-3I+psCOaf`KaL;mV*S8d$5VFNnmjv(3$8~sl7bsxzM z4(EEit%Jk)!TzhR8y@OgIRLKCjRm#`CkWh_x9B#6HPk)WXYE3;N}*IK?5cu1;odSS zg)T_r9t`u!F*rRphFCst4RjA3I7-{!H88NZ>UKdR?pZn5KiJne;Kr^EE3@Hzp3UES zVBYn1vcmy`vOA6@a@!mIXkQafkn8iH5y4q`YkR?sR%Y)KIh+TJRoLgxPH}Ld*NTof z^Jl6d2ZcVG%kyW*y)D^6Vg7uqLGue`d;s%_#-*=PTs@(#Oxp9C8YkI9NCr%CL}bAkCi#a^YpUmJ^byoIev_ zilXb$Uav7@^Jn1tTb21UmJ5k@oyx+wP8gL62G|j$-qP56{!G!Gg4wFt_1K!SW6VIR zYyM1fLn$<)RksqkoFSM$i?bxRln!7p0;oA0!O2`x8i9Fs=SXL|))1u+>8-;I`15aY zL+l`2woCsU8I$?*QKQ74YtxtM`LkgZ2QDu^@M75H#+4T z(+D-Xqxttj-iY>2)QO{86`u?SPzi^DY`+~9+$w@QpuWo*vF9+MKLrBN6{dyP;~dQc z+_};m?JrzSn5M6xz)y+SgPTlWcO%AXK;uA3P@-g4(T+CwMWCNW=!xd)5$Q(e}Ob)wTcn0M*^5pBO6T_He~ zxFfA8ICETOCspg|E!3Cf&JrR&xwORc!J8#E?B!zvzj}VYV#jQeK`ux~xl{64F?ECC zi3y+@(NLW~TaMVhTY@(UbFFup=FcvivpqY#)r9TXt%FUkUx@{mOT5xGf-Da=w_2Fq zrNwPmuQ-AIF$PBoB2oop1*bvA`8(}6;wvgS*j3&HdAcpHc{5-apcod;`AWCgYOf2O zfT%?7Ix^i@DBH7A*?~|J!u+7S=m#^DfL{dPb6}_Q({VzpOj`w-kao~LI*z4a)38kt znC?3*oDbq9db0M3TdzEUDTy}A^kz1Sm%jM4+(;*jwWYQ^K_9zu{xd;oseQ_lE$cM8 zv8<&S;p{_zxvIc0L?^it1-UHEr8k{^>tLJHhjnX%6?@B#3KQ{;vosQ=>;YGBFFLIX zr6*IpV5_cho!X*mvd1bGa|@GfzIY9zHFSizOK#I~>vi=g-x&bd}N ze+Cj_(U1r(toHGT6 zkLOaJ&zt>B@uT3 z{rvfI>U50Qb+=lIKz#lL}v2`LESZ-P5K%+sz(myRxLU;!#Hwlb?G z9T#pP=Y?_&nBsx;Z#MbsN@8tX<%cLkPWL72*KOE^}H}u)atk zP4OA*xV&&K29wFE5xJCP6Ie9BkTrXb(^+69mY!<(pP#N~S0Lu(Bel*rNcVE^mk3GM z!r->0-0w`;H8*>O3?8&{hu$Ge7?Pxw8K4tZ!Yzt|8*zi0Xplj)kl!z%2bf&Agq0B- zilO}V zQFE(Zov;g_4}u7Cm1Wo1sFMb1@QQ)vAYv~)pT3EgH~?AIGX|vlTO)$$11MOvUZPgJj0!hw8Sy&_DfEH7fHHpea#~C(kMt6GnnCu0 z+7YT`pFo_A=KE=h{+O%6+hEmDfi*v0iq+gM+X0ic^hnHhYCFXFBQ+y-xy$N~ij*27 zU05Mk)>WQ^3@3_aimi6p?OHT%;yS|6EZ3CS>IWHJ($FYYcaBgZ`hzPIaU&zgm>1{F z=4kZ)+d2QAOtiSCU5G`+#iC|lve_j{nj(sd;-!{=2EjUsT_9aau@q+FkTQUF&>+eu zyXc3Fq0>&$&-i`y`Ljj3E7a&9CY3CA+OOnJlcnPrmwid{zRpHscnfw+dasjD5Rse* za25h!EFy>9l0-;@!|KfzKeeL>sC_kFaQi7zrF?}?`<;rxq1KQS&q2$@MH!gaou-PH zQ==>!p(b^$-Q|wmNRp9K=hh|?2*q>^=VfR-B})8G6{1SD@cwE z{seJllN&FV%4#n)gprvSwi0%{1I|TI@f@pNV)8nF`)R&!2Sh8C$DB&Y>`*PyD&+3e zZQ*)fGXq!)?4&uRY{!~N6rX@C4b%Dxv&6-o`%X&#;BN<8Eve)Q{yI{3V_7WSuH_B&rxV%^KF^K&D>)1g3K=@nILE5njGm?y(hP5 zlp8dBphdUjf{<>>H6+6-klOK*^t4%c{rq{&J*jUMHciNJT}a-NN?B7N!3K&FKYT?4 zd>Fckbaq)|8rDmWnTXshM)Z+|96dWoGZ&n1LNHT=z_23(14q989BD{SCfX#wl8P1= zE8;YEgWp3N>NE1dNxJ?tXQJf#(1Pq=GB8#O3^udD-3(~YzQ~E*^D2*n#1eExp(={%;H75GM zutdu-+%%Bmj6S>dbf+%c)Xa3v4Ve#C%?)4)xQCRyu{j@~xg-~;S5(SguzRSZDWY0p z_y9)Q9GQeROuk24u~t1eW(x2C^Z+eWOJuIrh+ZkfpdWi25swUebGM9Ur4b&GO&5Gx zKOK-D#fl64>eqxO@}yWFCln)_p79mQi;d#+gCU7ZBi>2{1wk@PiVYhH=^}^Mr~86* z$TU-JHDzEIUAJTPk|y<){;Vj;Hb@989yQaJLhYopo9<{WNeWo4?m()?8|^|u?!w7v z>{pX*wWfYaC%$k;LasONS~j=D)+mP)%e6VmWOJgFlRb^(@c)KMI<2I{yQ3^4<_oiH z_+`6fdgxxbcmxCdA&dX!shMicaHOs2=Z_Hnhdb>`HfYHNRR{djIWjl&3AQa{GEr<6>xpoI=b|JVl#EVt`lawIfY@7+%3 zY}p0d)G|dWQ_L_8q!T@oFJ1Zc4oawgl#{8E5B7G2KKO#Q$twkM>NtV$`yN$X4iAo>s!q zbrFwzmgewERbBzIKTW&OV3F%w$I4)nzKz-Pmo^cEoC~6>XY@bF6zBy-J}2K-3V}dZ z!#k5iGeMF>0h?FpkKRwDmbthZvF2#4aZO!v(mp4xGWjNO(43-d;qEx__#3zD@c!Jz zRiOc`OnO6x?l3})ItkRvjEg>cEK`l{>>avVfW|SNFP{ZMlgV8qMZMriMKPM#g-2a0 zbkmW<&?LGtEhKjw?Ucnbw;W+utV-1m`FlXJaDrmK@0gZ1Wy+f~<*PI0Et&GxOnF, 2006. +# +# , fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 10:47+1100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/manipulators.py:306 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "un" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Lūdzu ievadiet korektu %s" + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Atdaliet vairākus ID ar komatiem." + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: oldforms/__init__.py:352 newforms/fields.py:78 newforms/fields.py:373 +#: newforms/fields.py:449 newforms/fields.py:460 +msgid "This field is required." +msgstr "Šis lauks ir obligāts." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Vērtībai ir jābūt veselam skaitlim." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Vērtībai jābūt True vai False." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Šis lauks nevar būt null" + +#: db/models/fields/__init__.py:454 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Ievadiet korektu datumu YYYY-MM-DD formātā." + +#: db/models/fields/__init__.py:521 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Ievadiet korektu datumu/laiku YYYY-MM-DD HH:MM formātā." + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "Ievadiet korektu faila vārdu." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "" + +#: conf/global_settings.py:59 +msgid "Polish" +msgstr "" + +#: conf/global_settings.py:60 +msgid "Brazilian" +msgstr "" + +#: conf/global_settings.py:61 +msgid "Romanian" +msgstr "" + +#: conf/global_settings.py:62 +msgid "Russian" +msgstr "" + +#: conf/global_settings.py:63 +msgid "Slovak" +msgstr "" + +#: conf/global_settings.py:64 +msgid "Slovenian" +msgstr "" + +#: conf/global_settings.py:65 +msgid "Serbian" +msgstr "" + +#: conf/global_settings.py:66 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:67 +msgid "Tamil" +msgstr "" + +#: conf/global_settings.py:68 +msgid "Turkish" +msgstr "" + +#: conf/global_settings.py:69 +msgid "Ukrainian" +msgstr "" + +#: conf/global_settings.py:70 +msgid "Simplified Chinese" +msgstr "" + +#: conf/global_settings.py:71 +msgid "Traditional Chinese" +msgstr "" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "gads" +msgstr[1] "gadi" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mēnesis" +msgstr[1] "mēneši" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "nedēļa" +msgstr[1] "nedēļas" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "diena" +msgstr[1] "dienas" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "stunda" +msgstr[1] "stundas" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minūte" +msgstr[1] "minūtes" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Pirmdiena" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Otrdiena" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Trešdiena" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Ceturdiena" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Piektdiena" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sestdiena" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Svētdiena" + +#: utils/dates.py:14 +msgid "January" +msgstr "Janvāris" + +#: utils/dates.py:14 +msgid "February" +msgstr "Februāris" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marts" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Aprīlis" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maijs" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Jūnijs" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Jūlijs" + +#: utils/dates.py:15 +msgid "August" +msgstr "Augusts" + +#: utils/dates.py:15 +msgid "September" +msgstr "Septembris" + +#: utils/dates.py:15 +msgid "October" +msgstr "Oktobris" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembris" + +#: utils/dates.py:16 +msgid "December" +msgstr "Decembris" + +#: utils/dates.py:19 +msgid "jan" +msgstr "" + +#: utils/dates.py:19 +msgid "feb" +msgstr "" + +#: utils/dates.py:19 +msgid "mar" +msgstr "" + +#: utils/dates.py:19 +msgid "apr" +msgstr "" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jūn" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jūl" + +#: utils/dates.py:20 +msgid "aug" +msgstr "aug" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sep" + +#: utils/dates.py:20 +msgid "oct" +msgstr "okt" + +#: utils/dates.py:20 +msgid "nov" +msgstr "" + +#: utils/dates.py:20 +msgid "dec" +msgstr "" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "" +msgstr[1] "" + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "Pārnešana jaunā rindā šeit nav atļauta." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:150 +#: newforms/widgets.py:162 +msgid "Unknown" +msgstr "Nezināms" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "Yes" +msgstr "Jā" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "No" +msgstr "Nē" + +#: oldforms/__init__.py:667 core/validators.py:173 core/validators.py:442 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "Jūsu norādītais fails ir tukšs." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Ievadiet veselu skaitli intervālā no -32,768 līdz 32,767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Ievadiet pozitīvu skaitli." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Ievadiet veselu skaitli intervāla starp 0 un 32,767." + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "sesijas atslēga" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "sesijas dati" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "beigu datums" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sesija" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sesijas" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "" + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "" + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Izskatās, ka Jūsu pārlūks neatbalsta cookies. Cookies ir obligātas, lai " +"pieslēgtos." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Lūdzu ievadiet lietotājvārdu un paroli. Atceraties ka abi lauki ir " +"reģistrjūtīgi." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "" + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "" + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "" + +#: contrib/auth/views.py:39 +#, fuzzy +msgid "Logged out" +msgstr "Izlogoties" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nosaukums" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "kods" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "tiesība" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "tiesības" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "grupa" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "grupas" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "lietotāja vārds" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "vārds" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "uzvārds" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "e-pasta adrese" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "parole" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "personāla statuss" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "" +"Atzīmējiet, ja vēlaties, lai lietotājs var pieslēgties administrācijas lapā." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "aktīvs" + +#: contrib/auth/models.py:96 +#, fuzzy +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Atzīmējiet, ja vēlaties, lai lietotājs var pieslēgties administrācijas lapā." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "superlietotāja statuss" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "pēdējoreiz pieslēdzies" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "datums, kad pievienojies" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Papildus manuāli piešķirtajām atļaujām, šis lietotājs papildus iegūs visas " +"atļaujas, kas piešķirtas grupām, kurās lietotājs atrodas." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "lietotāja atļaujas" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "lietotājs" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "lietotāji" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Personīgā informācija" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Atļaujas" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Svarīgi datumi" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Grupas" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "ziņojums" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "python modeļa klases nosaukums" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "satura tips" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "novirzīt(redirect) no" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Tam jābūt absolūtajam ceļam, ieskaitot domēna vārdu. Piemēram: '/events/" +"search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "novirzīt(redirect) uz" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Tas ir vai nu absolūtais ceļš (kā pirms tam) vai pilnais URL, kas sākas ar " +"'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "novirzīt" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "novirzījumi" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:315 +msgid "URL" +msgstr "" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Piemēram: '/about/contact/'. Pārliecinieties, ka esat ievietojuši sākuma un " +"beigu slīpsvītras." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "virsraksts" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "saturs" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "ieslēgt komentārus" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "šablona nosaukums" + +#: contrib/flatpages/models.py:13 +#, fuzzy +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Piemēram: 'flatpages/contact_page'. Ja tas nav norādīts, sistēma lietos " +"'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "reģistrācija obligāta" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Ja tas ir atzīmēts, tikai lietotāji, kas ir pieslēgušies sistēmās redzēs šo " +"lapu." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "vienkārša lapa" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "vienkāršas lapas" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "objekta ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "virsraksts" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "komentārs" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "reitings #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "reitings #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "reitings #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "reitings #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "reitings #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "reitings #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "reitings #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "reitings #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "korekts reitings" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "ievietošanas datums/laiks" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "publisks" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "IP adrese" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "idzēsts" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Atķeksējiet, ja komentārs ir neatbilstošs. Paziņojums A \"Šis komentārs ir " +"izdzēsts\" tiks parādīts tai vietā." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "komentāri" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Satura objekts" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Pievienojis %(user)s, %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "personas vārds" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "ip adrese" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "apstiprinājusi administrācija" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "brīvais komentārs" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "brīvie komentāri" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "rezultāts" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "rezultāta datums" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "karmas rezultāts" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karmas rezultāti" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d reitingu publicējis %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Šo komentāru atzīmējis %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "atzīmēšanas datums" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "lietotāja atzīme" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "lietotāja atzīmes" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Atzīmējis %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "dzēšanas datums" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "moderācijas dzēšana" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "moderācijas dzēšanas" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Moderācijas dzēšana, veicis %r" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Lietotāja vārds:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "Izlogoties" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Parole:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Esat aizmirsis savu paroli?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Reitings" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Pieprasīts" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Neobligāts" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Ievietojiet fotogrāfiju" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Komentārs:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Pirmsskatīt komentāru" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Jūsu vārds:" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonīmie lietotāji nedrīkst balsot" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Invalīds komentāru ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Nedrīkst balsot par sevi" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Šis reiting ir obligāts jo Jūs ievietojāt vismaz vienu citu reitingu." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Šo komentāru ir ievietojis lietotājs, kas ievietojis mazāk kā %(count)s " +"komentāru:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Šo komentāru ir ievietojis lietotājs, kas ievietojis mazāk kā %(count)s " +"komentārus:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Šo komentāru ieviejis paviršs lietotājs:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Atļauti tikai POST izsaukumi" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Viens vai vairāki pieprasītie lauki netika ievadīti" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Kāds ir iejaucies komentāru formā (drošības traucējums)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"Komentāru forma ir nekorekts 'target' parametrs -- objekta ID bija nepareizs" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Komentāru forma nenodrošināja 'preview' vai 'post'" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domēna vārds" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "izvadāmais vārds" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "saits" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "saiti" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                            By %s:

                                                                            \n" +"
                                                                              \n" +msgstr "" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Visi" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Jebkuršs datums" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Šodien" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Pēdējās 7 dienas" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Šomēnes" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Šogad" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "darbības laiks" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "objekta id" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "objekta attēlojunms" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "darbības atzīme" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "momainīt paziņojumu" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "žurnāla ieraksts" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "žurnāla ieraksti" + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "Visi datumi" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +msgid "Home" +msgstr "Sākums" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Dokumentācija" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Grāmatzīmes" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Paroles maiņa" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Dokumentācijas grāmatzīmes" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                              To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                              \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dokumentācija šai lapai" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Pārvieto jūs no jebkuras lapas dokumentācijā uz skatu, kas ģenerē šo lapu." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Parādīt objekta ID" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Parāda content-type un unikālo ID lapām, kas atspoguļo vientuļu objektu." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Labot šo objektu (patreizējā logā)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Pāriet uz admininstrācijas lapu tām lapām, kas atspoguļo vientuļu objektu." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Labot šo lapu (jaunā logā)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Tāpat kā iepriekš, tikai atver administrācijas lapu jaunā logā." + +#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:9 +msgid "Delete" +msgstr "Dzēst" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Saglabāt kā jaunu" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Saglabāt un pievienot vēl vienu" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Saglabāt un turpināt labošanu" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Saglabāt" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Servera kļūda" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Servera kļūda (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Servera kļūda (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Ir notikusi kļūda. Tas ir paziņots saita administratoriem ar e-pasta " +"starpniecību un visdrīzākajā laikā tiks izlabots. Paldies par sapratni." + +#: contrib/admin/templates/admin/filter.html:2 +#, fuzzy, python-format +msgid " By %(filter_title)s " +msgstr "Pēc %(title)s " + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Aiziet!" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Vēsture" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Datums/laiks" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Lietotājs" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "darbība" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. N Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Šim objektam nav izmaiņu vēsture. Tas visdrīzāk nav pievienots izmantojot " +"administrācijas saitu." + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, fuzzy, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Izdzēšot objektu %(object_name)s '%(object)s' tiks dzēsti visi saistītie " +"objekti , bet Jums nav tiesību dzēst sekojošus objektu tipus:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, fuzzy, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Vai esat pārliecināts, ka vēlaties dzēst %(object_name)s \"%(object)s\"? " +"Tiks dzēsti sekojoši objekti:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Jā, es esmu pārliecināts" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Pievienot %(name)s" + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Pievienot" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Apskatīt saitā" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Lūdzu izlabojiet kļūdu zemāk" +msgstr[1] "Lūdzu izlabojiet kļūdas zemāk" + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Sakārtošana" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Sakārtojums:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Sveicināti," + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Lapa nav atrasta" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Mēs atvainojamies, bet pieprasītā lapa nevar tikt atrasta." + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "Pieslēdzieties" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modeļi, kas pieejami %(name)s aplikācijā." + +#: contrib/admin/templates/admin/index.html:18 +#, fuzzy, python-format +msgid "%(name)s" +msgstr "Pievienot %(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Izmainīt" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Jums nav tiesības jebko labot." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Nesenās darbības" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Manas darbības" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nav pieejams" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django saita administrācija" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django administrēšana" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +#, fuzzy +msgid "Username" +msgstr "Lietotāja vārds:" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +#, fuzzy +msgid "Password" +msgstr "Parole:" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#, fuzzy +msgid "Password (again)" +msgstr "Paroles maiņa" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Patreiz:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Nomainīt:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Datums:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Laiks:" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Paldies par pavadīto laiku māja lapā." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Pieslēgties vēlreiz" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Jūs esat saņēmuši šo e-pastu sakarā ar Jūsu pieprasīto paroles pārstatīšanu" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "Jūsu lietotāja kontam %(site_name)s saitā" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Jūsu jaunais parole ir: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Jūs vienmēr varat nomainīt šo paroli aizejot uz šo lapu:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Jūsu lietotājvārds, ja gadījumā Jūs esat to aizmirsis:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Paldies par mūsu saita lietošanu!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s komanda" + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "Paroles pārstatīšana(reset)" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Paroles pārstatīšana sekmīga" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Mēs aizsūtījām pa e-pastu jaunu paroli, ko Jūs esat apstiprinājis. Jūs to " +"drīzumā saņemsiet." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Paroles maiņa" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Drošības nolūkos ievadiet veco paroli un pēc tam ievadiet Jūsu jauno paroli " +"divreiz lai mēs varētu pārbaudīt, vai tā ir uzrakstīta pareizi." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Vecā parole:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Jaunā parole:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Apstiprināt paroli:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Nomainīt manu paroli" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Paroles nomaiņa sekmīga" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Jūsu parole ir nomainīta." + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Esat aizmirsuši savu paroli? Ievadiet e-pasta adresi zemāk un mēs " +"pārstatīsim Jūsu paroli un aizsūtīsim jaunu pa e-pastu." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-pasta adrese:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Paroles pārstatīšana" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Saita administrācija" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" pievienots sekmīgi." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "Jūs varat labot to atkal zemāk." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Jūs varat pievienot vēl vienu %s zemāk." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Pievienot %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Pievienots %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Izmainīts %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Izdzēsts %s" + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Neviens lauks nav izmainīts" + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" nomainīts sekmīgi." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" pievienots sekmīgi. Jūs to varat regiģēt zemāk." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Izmainīt %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" sekmīgi izdzēsts." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Vai esat pārliecināts?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Izmainīt vēsturi: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Izvēlēties %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Izvēlēties %s lai izmainītu" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Lūdzu pieslēdzieties vēlreiz, jo jūsu sesija ir novecojusi. Neuztraucieties: " +"Jūsu ievadītie dati ir saglabāti." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Izskatās, ka Jūsu pārlūks neatbalsta sīkdatnes (cookies). Lūdzu ieslēdziet " +"sīkdatņu atbalstu, pārlādējiet lapu un mēģiniet vēlreiz." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Lietotājvārdi nevar saturēt simbolu '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Jūsu e-pasta adrese nav jūsu lietotājvārds. Lietojiet '%s' tā vietā." + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "" + +#: contrib/admin/views/doc.py:164 +#, fuzzy, python-format +msgid "App %r not found" +msgstr "Lapa nav atrasta" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Vesels skaitlis" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Boolean (Pareizs vai Nepareizs)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Virkne (līdz pat %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Ar komatu atdalīti veselie skaitļi" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Datums (bez laika)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Datums (ar laiku)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "E-pasta adrese" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Faila ceļš" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Decimāls skaitlis" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Loģiskais (Pareizs vai Nepareizs)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relācija uz vecāka modeli" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Telefona numurs" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Teksts" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Laiks" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "ASV štats (divi augšējā reģistra burti)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML teksts" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "" + +#: contrib/admin/views/auth.py:30 +#, fuzzy +msgid "Add user" +msgstr "Pievienot %s" + +#: contrib/admin/views/auth.py:57 +#, fuzzy +msgid "Password changed successfully." +msgstr "Paroles nomaiņa sekmīga" + +#: contrib/admin/views/auth.py:64 +#, fuzzy, python-format +msgid "Change password: %s" +msgstr "Paroles maiņa" + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "" + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "" + +#: newforms/fields.py:126 core/validators.py:120 +msgid "Enter a whole number." +msgstr "Ievadiet veselus skaitļus." + +#: newforms/fields.py:128 +#, fuzzy, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Šai vērtībai jābūt %s pakāpei." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "" + +#: newforms/fields.py:163 +#, fuzzy +msgid "Enter a valid date." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:190 +#, fuzzy +msgid "Enter a valid time." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:226 +#, fuzzy +msgid "Enter a valid date/time." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:240 +#, fuzzy +msgid "Enter a valid value." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:269 core/validators.py:161 +msgid "Enter a valid e-mail address." +msgstr "Ievadiet korektu e-pasta adresi." + +#: newforms/fields.py:287 newforms/fields.py:309 +#, fuzzy +msgid "Enter a valid URL." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:311 +#, fuzzy +msgid "This URL appears to be a broken link." +msgstr "URL %s ir salauzta saite." + +#: newforms/fields.py:359 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: newforms/fields.py:377 newforms/fields.py:453 +#, fuzzy +msgid "Enter a list of values." +msgstr "Ievadiet korektu faila vārdu." + +#: newforms/fields.py:386 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "" + +#: template/defaultfilters.py:436 +msgid "yes,no,maybe" +msgstr "jā,nē,varbūt" + +#: views/generic/create_update.py:43 +#, fuzzy, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(name)s \"%(obj)s\" nomainīts sekmīgi." + +#: views/generic/create_update.py:117 +#, fuzzy, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(name)s \"%(obj)s\" sekmīgi izdzēsts." + +#: views/generic/create_update.py:184 +#, fuzzy, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(site_name)s komanda" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Šī vērtība var saturēt tikai burtus, numurus un apakšsvītras." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Šī vērtība var saturēt tikai burtus, numurus un apakšsvītras, svītras vai " +"šķērssvītras." + +#: core/validators.py:72 +#, fuzzy +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"Šī vērtība var saturēt tikai burtus, numurus un apakšsvītras, svītras vai " +"šķērssvītras." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Augšējā reģistra burti nav atļauti." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Apakšējā reģistra burti nav atļauti." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Ievadiet tikai numurus, kas atdalīti ar komatiem." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Ievadiet korektas e-pasta adreses, kas atdalītas ar komatiem." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Lūdzu ievadiet korektu IP adresi." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "Tukšas vērtības nav atļautas." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Ne ciparu simboli nav atļauti." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Šī vērtība nevar saturēt tikai ciparus." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Atļauti tikai alfabētiskie simboli." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "" + +#: core/validators.py:143 +#, fuzzy, python-format +msgid "Invalid date: %s." +msgstr "Nekorekts URL: %s" + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Ievadiet korektu laiku HH:MM formātā" + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Augšupielādējiet korektu attēlu. Fails, ko Jūs augšupielādējāt nav attēls " +"vai arī bojāts attēla fails." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "URL %s nesatur korektu attēlu." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Telefona numuriem jābūt XXX-XXX-XXXX formātā. \"%s\" is nekorekts." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "URL %s nenorāda uz korektu QuickTime video." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Reāls URL obligāts." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Korekts HTML ir obligāts. Specifiskās kļūdas:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Slikti formēts XML: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "Nekorekts URL: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "URL %s ir salauzta saite." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Ievadiet korektu ASV štata abriviatūru." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Seko saviem vārdiem! Vārds %s nav atļauts šeit." +msgstr[1] "Seko saviem vārdiem! Vārdi %s nav atļauts šeit." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Laukam jāsaskan ar %s lauku." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Lūdzu ievadiet kaut ko vismaz vienā laukā." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Lūdzu ievadiet abus laukus vai atstājiet abus tukšus." + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Šis lauks ir jāaizpilda, ja %(field)s ir vienāds %(value)s" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Šis lauks ir jāaizpilda, ja %(field)s nav vienāds %(value)s" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "Duplicētas vērtības nav atļautas." + +#: core/validators.py:364 +#, fuzzy, python-format +msgid "This value must be between %s and %s." +msgstr "Šai vērtībai jābūt %s pakāpei." + +#: core/validators.py:366 +#, fuzzy, python-format +msgid "This value must be at least %s." +msgstr "Šai vērtībai jābūt %s pakāpei." + +#: core/validators.py:368 +#, fuzzy, python-format +msgid "This value must be no more than %s." +msgstr "Šai vērtībai jābūt %s pakāpei." + +#: core/validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "Šai vērtībai jābūt %s pakāpei." + +#: core/validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "Lūdzu ievadiet korektu decimālu numuru." + +#: core/validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu %s." +msgstr[1] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu %s." + +#: core/validators.py:422 +#, fuzzy, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu %s." +msgstr[1] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu %s." + +#: core/validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu aiz komata " +"%s." +msgstr[1] "" +"Lūdzu ievadiet korektu decimālu numuru ar maksimālu ciparu skaitu aiz komata " +"%s." + +#: core/validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Pārliecinieties, ka jūsu augšupielādētais fails ir vismaz %s baiti liels." + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Pārliecinieties, ka jūsu augšupielādētais fails ir maksimums %s baiti liels." + +#: core/validators.py:453 +msgid "The format for this field is wrong." +msgstr "Šī faila formāts ir nekorekts." + +#: core/validators.py:468 +msgid "This field is invalid." +msgstr "Šis lauks ir nekorekts." + +#: core/validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Nevar neko no %s" + +#: core/validators.py:507 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "URL %(url)s atgrieza nekorektu Content-Type headeri '%(contenttype)s'." + +#: core/validators.py:540 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Lūdzu aiztaisiet neaiztaisīto %(tag)s tagu no rindas nr %(line)s. (Rinda " +"sākas ar \"%(start)s\".)" + +#: core/validators.py:544 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:549 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:554 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:558 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:563 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#~ msgid "Use '[algo]$[salt]$[hexdigest]'" +#~ msgstr "Lietojiet '[algo]$[salt]$[hexdigest]'" + +#~ msgid "Have you forgotten your password?" +#~ msgstr "Vai esat aizmirsis paroli?" diff --git a/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/lv/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..b81550aedb3816b514e7ed0468328e9caddfe251 GIT binary patch literal 367 zcwR-1!A=4(5QZ^&+M{O=J$S>xE)m6wgvwUj*xe<|LZY_{Wv#)Mw1wz{_lfbI%PCLV>*1KBZg+I zoTNn_m2z#Yhs@P#0e73)L1hSv#zh4~D}@hBkO(gcYGZr_xD9a!#h6@NHscuMAVMj( z%I&=;MoY*FNGN(JNR~FAi=0JVI1iN-+O32)mZ1A7=P0KcLp_$!ln-c, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 10:46+1100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "" diff --git a/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..5ab5aa6cbbbc85764c14949c9515f2deeba6bf70 GIT binary patch literal 50847 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.po new file mode 100644 index 0000000..f23d8ed --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/django.po @@ -0,0 +1,2320 @@ +# translation of mk_django.po to Macedonian +# +# Georgi Stanojevski , 2006, 2007. +msgid "" +msgstr "" +"Project-Id-Version: mk_django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 10:53+1100\n" +"PO-Revision-Date: 2007-02-24 13:53+0100\n" +"Last-Translator: Georgi Stanojevski \n" +"Language-Team: Macedonian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=2; plural=n != 1;" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s од овој тип %(type)s веќе постои за даденото %(field)s." + +#: db/models/manipulators.py:306 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "и" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "Ве молам внесете правилно %s." + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "Одвојте ги идентификационите броеви со запирки." + +#: db/models/fields/related.py:644 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Држете го „Control“ или „Command“ на Мекинтош за да изберете повеќе од едно." + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Ве молам внесете правилен %(self)s идентификацион број. Оваа вредност %(value)r е неправилна." +msgstr[1] "Ве молам внесете правилен %(self)s идентификацион број. Вредностите %(value)r се неправилни." + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s со ова %(fieldname)s веќе постои." + +#: db/models/fields/__init__.py:116 db/models/fields/__init__.py:273 +#: db/models/fields/__init__.py:605 db/models/fields/__init__.py:616 +#: oldforms/__init__.py:352 newforms/fields.py:78 newforms/fields.py:373 +#: newforms/fields.py:449 newforms/fields.py:460 +msgid "This field is required." +msgstr "Ова поле е задолжително." + +#: db/models/fields/__init__.py:366 +msgid "This value must be an integer." +msgstr "Оваа вредност мора да биде цел број." + +#: db/models/fields/__init__.py:401 +msgid "This value must be either True or False." +msgstr "Оваа вредност мора да биде или точно или неточно." + +#: db/models/fields/__init__.py:422 +msgid "This field cannot be null." +msgstr "Оваа вредност неможе да биде null." + +#: db/models/fields/__init__.py:454 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Внесете правилен датум во форматот ГГГГ-ММ-ДД." + +#: db/models/fields/__init__.py:521 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Внесете правилен датум/време во форматот ГГГГ-ММ-ДД ЧЧ:ММ." + +#: db/models/fields/__init__.py:625 +msgid "Enter a valid filename." +msgstr "Внесите правилно име на датотека." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Арапски" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Бенгалски" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "Чешки" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "Велшки" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "Дански" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "Германски" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "Грчки" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "Англиски" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "Шпански" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Аргентиско шпански" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "Фински" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "Француски" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "Галски" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "Унгарски" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "Еврејски" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "Исландски" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "Италијански" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "Јапонски" + +#: conf/global_settings.py:57 +msgid "Latvian" +msgstr "Латвиски" + +#: conf/global_settings.py:58 +msgid "Macedonian" +msgstr "Македонски" + +#: conf/global_settings.py:59 +msgid "Dutch" +msgstr "Холандски" + +#: conf/global_settings.py:60 +msgid "Norwegian" +msgstr "Норвешки" + +#: conf/global_settings.py:61 +msgid "Polish" +msgstr "Полски" + +#: conf/global_settings.py:62 +msgid "Brazilian" +msgstr "Бразилски" + +#: conf/global_settings.py:63 +msgid "Romanian" +msgstr "Романски" + +#: conf/global_settings.py:64 +msgid "Russian" +msgstr "Руски" + +#: conf/global_settings.py:65 +msgid "Slovak" +msgstr "Словачки" + +#: conf/global_settings.py:66 +msgid "Slovenian" +msgstr "Словенечки" + +#: conf/global_settings.py:67 +msgid "Serbian" +msgstr "Српски" + +#: conf/global_settings.py:68 +msgid "Swedish" +msgstr "Шведски" + +#: conf/global_settings.py:69 +msgid "Tamil" +msgstr "Тамил" + +#: conf/global_settings.py:70 +msgid "Turkish" +msgstr "Турски" + +#: conf/global_settings.py:71 +msgid "Ukrainian" +msgstr "Украински" + +#: conf/global_settings.py:72 +msgid "Simplified Chinese" +msgstr "Упростен кинески" + +#: conf/global_settings.py:73 +msgid "Traditional Chinese" +msgstr "Традиционален кинески" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "година" +msgstr[1] "години" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "месец" +msgstr[1] "месеци" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "седмица" +msgstr[1] "седмици" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "ден" +msgstr[1] "денови" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "час" +msgstr[1] "часови" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "минута" +msgstr[1] "минути" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "понеделник" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "вторник" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "среда" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "четврток" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "петок" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "сабота" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "недела" + +#: utils/dates.py:14 +msgid "January" +msgstr "јануари" + +#: utils/dates.py:14 +msgid "February" +msgstr "февруари" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "март" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "април" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "мај" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "јуни" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "јули" + +#: utils/dates.py:15 +msgid "August" +msgstr "август" + +#: utils/dates.py:15 +msgid "September" +msgstr "септември" + +#: utils/dates.py:15 +msgid "October" +msgstr "октомври" + +#: utils/dates.py:15 +msgid "November" +msgstr "ноември" + +#: utils/dates.py:16 +msgid "December" +msgstr "декември" + +#: utils/dates.py:19 +msgid "jan" +msgstr "јан" + +#: utils/dates.py:19 +msgid "feb" +msgstr "фев" + +#: utils/dates.py:19 +msgid "mar" +msgstr "мар" + +#: utils/dates.py:19 +msgid "apr" +msgstr "апр" + +#: utils/dates.py:19 +msgid "may" +msgstr "мај" + +#: utils/dates.py:19 +msgid "jun" +msgstr "јун" + +#: utils/dates.py:20 +msgid "jul" +msgstr "јул" + +#: utils/dates.py:20 +msgid "aug" +msgstr "авг" + +#: utils/dates.py:20 +msgid "sep" +msgstr "сеп" + +#: utils/dates.py:20 +msgid "oct" +msgstr "окт" + +#: utils/dates.py:20 +msgid "nov" +msgstr "ное" + +#: utils/dates.py:20 +msgid "dec" +msgstr "дек" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "јан." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "фев." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "авг." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "сеп." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "окт." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "ное." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "дек." + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "F j" + +#: oldforms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Осигурајте се дека вашиот текст има помалку од %s знак." +msgstr[1] "Осигурајте се дека вашиот текст има помалку од %s знаци." + +#: oldforms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "Тука не се дозволени прекини на линија." + +#: oldforms/__init__.py:493 oldforms/__init__.py:566 oldforms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Изберете правилно, %(data)s' не е во %(choices)s." + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:150 +#: newforms/widgets.py:162 +msgid "Unknown" +msgstr "Непознато" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "Yes" +msgstr "Да" + +#: oldforms/__init__.py:572 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:162 +msgid "No" +msgstr "Не" + +#: oldforms/__init__.py:667 core/validators.py:173 core/validators.py:442 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Не беше пратена датотека. Проверете го типот на енкодирање на формата." + +#: oldforms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "Пратената датотека е празна." + +#: oldforms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Внесете цел број помеѓу -32,768 и 32,767." + +#: oldforms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Внесете позитивен број." + +#: oldforms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Внесете цел број помеѓу 0 и 32,767." + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "клуч на сесијата" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "податоци од сесијата" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "датум на истекување" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "сесија" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "сесии" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "Двете полиња со лозинките не се совпаѓаат." + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "Веќе постои корисник со тоа корисничко име." + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Не изгледа дека вашиот прелистувач има овозможено колачиња. Колачињата се потребни за да се најавите." + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Ве молам внесете точно корисничко име и лозинка. Имајте на ум дека и во " +"двете полиња се битни големите и малите букви." + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "Оваа сметка е неактивна." + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "Нема регистрирано корисник со оваа адреса за е-пошта. Сигурни ли сте дека сте регистрирани?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "Двете нови лозинки не се совпаѓаат." + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "Не ја внесовте точно вашата стара лозинка. Ве молам внесете ја повторно." + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Одјавен" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "име" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "кодно име" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "привилегија" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "привилегии" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "група" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "групи" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "корисничко име" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"Дозволени се најмногу 30 знаци. Дозволени се само алфанумерички знаци " +"(букви, цифри и долна црта)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "име" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "презиме" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "е-пошта" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "лозинка" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "Користете '[algo]$[salt]$[hexdigest]' или користете ја формата за промена на лозинката." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "статус на администраторите" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Означува дали корисникот може да се логира во сајтот за администрација." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "активен" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Означува дали корисникот може да се логира. Одштиклирајте го ова наместо да " +"бришете корисници." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "статус на суперкорисник" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"Означува дека овој корисник ги има сите привилегии без експлицитно да се " +"доделуваат сите." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "последна најава" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "датум на зачленување" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Како дополнување на рачно доделени привилегии, овој корисник ќе ги добие " +"автоматски и сите привилегии за секоја група во која тој/таа членува." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "кориснички привилегии" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "корисник" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "корисници" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Лични информации" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Привилегии" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Важни датуми" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Групи" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "порака" + +#: contrib/contenttypes/models.py:26 +msgid "python model class name" +msgstr "има на класата на питон моделите" + +#: contrib/contenttypes/models.py:29 +msgid "content type" +msgstr "content type" + +#: contrib/contenttypes/models.py:30 +msgid "content types" +msgstr "content types" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "пренасочено од" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Ова треба да биде апсолутна патека без името на домејнот. На пр. „/nastani/" +"prebaraj/“." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "пренасочи кон" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Ова може да биде или апсолутна патека (како погоре) или цела адреса " +"почувајќи со „http://“." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "пренасочување" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "пренасочувања" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:315 +msgid "URL" +msgstr "URL" + +#: contrib/flatpages/models.py:8 +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"На пр. „/za/kontakt/“. Осигурајте се да имате коса црта и на крајот и на " +"почетокот." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "наслов" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "содржина" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "овозможи коментари" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "име на шаблонот" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"На пр. „flatpages/kontakt.html'. Ако не го внесете ова, системот ќе користи " +"„flatpages/default.html“." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "потребна е регистрација" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Ако ова е штиклирано, само најавените корисници ќе можат да ја гледаат оваа " +"страница." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "статична страница" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "статични страници" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "object ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "наслов" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "коментар" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "популарност #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "популарност #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "популарност #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "популарност #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "популарност #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "популарност #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "популарност #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "популарност #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "е валидна популарност" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "датум/време пријавен" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "е јавен" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "ИП адреса" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "е отстранет" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Штиклирајте го ова поле ако коментарот не е пригоден. Наместо него пораката " +"„Овој коментар беше отстранет“ ќе биде прикажана." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "коментари" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Content објект" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Напишан од %(user)s на %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "име на личноста" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "ип адреса" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "одобрено од администраторите" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "слободен коментар" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "слободни коментари" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "поени" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "датум поени" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "карма поен" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "карма поени" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d гласање за популарност од %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Овој коментар беше означен од %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "датум на означување" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "корисничка ознака" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "кориснички ознаки" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Означено од %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "датум на бришење" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "бришење од модератор" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "бришења од модератор" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Бришење од модератор од %r" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Корисник:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "Одјава" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Лозинка:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Ја заборавите вашата лозинка?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Популарност" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Потребно" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "По желба" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Објави фотографија" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Коментар:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Прегледај" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Вашето име:" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Анонимните корисници неможе да гласаат" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Невалидно ИД на коментарот" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Нема гласање за самиот себе" + +#: contrib/comments/views/comments.py:27 +msgid "This rating is required because you've entered at least one other rating." +msgstr "" +"Ова гласање за популарност е потребно бидејќи внесовте најмалку уште едно " +"друго." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Овој коментар беше пратен од корисник кој пратил помалку од %(count)s " +"коментар:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Овој коментар беше пратен од корисник кој пратил помалку од %(count)s " +"коментари:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Овој коментар беше пратен од недоверлив корисник:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Дозволено е само POST" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Едно или повеќе од потребните полиња не беше пополнето" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Некој ја променил формата за коментари (сигурносен прекршок)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "Формата за коментар имаше неправилен „target“ параметар - идентификациониот број на објектот беше неправилен" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Формата за коментар не овозможи ниту „преглед“ ниту „праќање“" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "домејн" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "име кое се прикажува" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "сајт" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "сајтови" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                              By %s:

                                                                              \n" +"
                                                                                \n" +msgstr "" +"

                                                                                Од %s:

                                                                                \n" +"
                                                                                  \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Сите" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Било кој датум" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Денеска" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Последните 7 дена" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Овој месец" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Оваа година" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "време на акција" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "идентификационен број на објект" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "object repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "знакче за акција" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "измени ја пораката" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "ставка во записникот" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "ставки во записникот" + +#: contrib/admin/templatetags/admin_list.py:238 +msgid "All dates" +msgstr "Сите датуми" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +msgid "Home" +msgstr "Дома" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Документација" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Обележувачи" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Промени лозинка" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Обележувачи на документација" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                  To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                  \n" +msgstr "" +"\n" +"

                                                                                  За да инсталирате обележувачи, влечете ја врската до " +"вашата\n" +"лента со алатки, или кликнете со десното копче и додадете го во вашите \n" +"обележувачи. Сега може да го изберете обележувачот од било која страница " +"на \n" +"сајтот. Имајте на ум дека за некои од овие обележувачи е потребно да го " +"гледате \n" +"сајтот од компјутер кој е означен како „внатрешен“ (разговарајте со вашиот \n" +"администратор ако не сте сигурни).

                                                                                  \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Документација за оваа страница" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Ве носи од било која страница од документацијата до погледот кој ја генерира " +"таа страница." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Прикажи идентификационен број на објектот" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Го прикажува типот на содржината и уникатниот идентификационен број за " +"страници кои претставуваат единечен објект." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Уреди го овој објект (во овој прозорец)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Скокнува до админ страницата за страници кои претставуваат единечен објект." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Уреди го овој објект (во нов прозорец)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Како погоре, но ја отвара админ страницата во нов прозорец." + +#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:9 +msgid "Delete" +msgstr "Избриши" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Сними како нова" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Сними и додади уште" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Сними и продолжи со уредување" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Сними" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Грешка со серверот" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Грешка со серверот (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Грешка со серверот (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Имаше грешка. Истата беше пријавена на администраторите и ќе биде поправена " +"во брзо време. Ви благодариме за вашето трпение." + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Од %(filter_title)s " + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Филтер" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"Нешто не е во ред со инсталацијата на базата на податоци. Потврдете дека " +"соодветни табели во базата се направени и потврдете дека базата може да биде " +"прочитана од соодветниот корисник." + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Оди" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 резултат" +msgstr[1] "%(counter)s резултати" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "вкупно %(full_result_count)s" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 +msgid "History" +msgstr "Историја" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Датум/час" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Корисник" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Акција" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j. Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Овој објект нема историја на измени. Најверојатно не бил додаден со админ " +"сајтот." + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "Бришење на %(object_name)s '%(escaped_object)s' ќе резултира со бришење на поврзаните објекти, но со вашата сметка немате доволно привилегии да ги бришете следните типови на објекти:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Сигурне сте дека сакате да ги бришете %(object_name)s „%(escaped_object)s“? " +"Сите овие ставки ќе бидат избришани:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Да, сигурен сум" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Прикажи ги сите" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "Додади %(name)s" + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Додади" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "Погледни на сајтот" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Ве молам поправете ја грешката подолу." +msgstr[1] "Ве молам поправете ги грешките подолу." + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "Подредување" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "Подреди:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Добредојдовте," + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Страницата не е најдена" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Се извинуваме, но неможе да ја најдеме страницата која ја баравте." + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "Најава" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Достапни модели во апликацијата %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Измени" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Немате дозвола ништо да уредува." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Последни акции" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Мои акции" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ништо не е достапно" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Џанго администрација на сајт" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Џанго администрација" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Прво, внесете корисничко име и лозинка. Потоа ќе можете да уредувате повеќе " +"кориснички опции." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Корисник" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +msgid "Password" +msgstr "Лозинка" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +msgid "Password (again)" +msgstr "Лозинка (повторно)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "Заради верификација внесете ја истата лозинка како и горе." + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "Внесете нова лозинка за корисникот %(username)s." + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Моментално:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Измена:" + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Датум:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Време:" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Ви благодариме што денеска поминавте квалитетно време со интернет страницава." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Логирајте се повторно" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Ја добивата оваа порака бидејќи побаравте да се ресетира вашата лозинка" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "за корисничката сметка на %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Вашата нова лозинка е: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Чуствувајте се слободно да ја промените оваа лозинка преку оваа страница:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Вашето корисничко име, во случај да сте го заборавиле:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Ви благодариме што го користите овој сајт!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Тимот на %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "Ресетирање на лозинка" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Успешно е ресетирањето на лозинката" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Ви пративме нова лозинка на адресата која ја внесовте.Треба да ја примите за " +"кратко време." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Измена на лозинка" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Заради сигурност ве молам внесете ја вашата стара лозинка и потоа внесете ја " +"новата двапати за да може да се потврди дека правилно сте ја искуцале." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Стара лозинка:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Нова лозинка:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Потврди лозинка:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Промени ја мојата лозинка" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Успешна промена на лозинката" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Вашата лозинка беше сменета." + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "Сте ја заборавиле вашата лозинка? Внесете ја вашата е-пошта подолу, ќе ја ресетираме вашата лозинка и новата ќе ви ја пратиме по е-пошта." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Е-пошта:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Ресетирај ја мојата лозинка" + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Администрација на сајт" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" беше успешно додаден." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "Подолу можете повторно да го уредите." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Подолу можете да додате уште еден %s." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Додади %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Додадено %s." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Изменета %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Избришана %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Не беше изменето ниедно поле." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" беше успешно изменета." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"%(name)s \"%(obj)s\" беше успешно додадена.Подолу можете повторно да ја " +"уредите." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Измени %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Еден или повеќе %(fieldname)s во %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Еден или повеќе %(fieldname)s во %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" беше избришана успешно." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Сигурни сте?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Историја на измени: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Изберет %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Изберете %s за измена" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Грешка во базата со податоци" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Ве молам најавете се повторно бидејќи вашата сесија е истечена. Не се " +"грижете. Вашите внесови беа зачувани." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Изгледа дека вашиот прелистувач не е конфигуриран да прифаќа колачиња. Ве " +"молам овозможете ги колачињата, превчитајте ја страта и пробајте повторно." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Корисничките имиња неможе да го содржат „@“ знакот." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Вашата е-пошта не е вашето корисничко име. Пробајте со „%s“." + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "таг:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "филтер:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "поглед:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "Не е најдена апликацијата %r" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Моделот %r не е најден во апликацијата %r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "повразните`%s.%s` објект" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "модел:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "поврзани `%s.%s` објекти" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "сите %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "број на %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Полиња на %s објекти" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Цел број" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Логичка (или точно или неточно)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Збор (до %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Целобројни вредности одделени со запирка" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Датум (без час)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Датум (со час)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Адреса на е-пошта" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Патека на датотека" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Децимален број" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Логичка (точно,неточно или празно)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Релација со родителскиот модел" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Телефонски број" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Текст" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Час" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Држава во САД (две големи букви)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML текст" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s не изгледа дека е url објект" + +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "Додади корисник" + +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." +msgstr "Успешна промена на лозинката." + +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "Промени лозинка: %s" + +#: newforms/fields.py:101 newforms/fields.py:254 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Осигурајте се дека оваа вредност има најмногу %d знаци." + +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Осигурајте се дека оваа вредност има најмалку %d знаци." + +#: newforms/fields.py:126 core/validators.py:120 +msgid "Enter a whole number." +msgstr "Внеси цел број." + +#: newforms/fields.py:128 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "Осигурајте се дека оваа вредност е помала или еднаква на %s." + +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "Осигурајте се дека оваа вредност е поголема или еднаква со %s." + +#: newforms/fields.py:163 +msgid "Enter a valid date." +msgstr "Внесете правилен датум." + +#: newforms/fields.py:190 +msgid "Enter a valid time." +msgstr "Внесете правилно време." + +#: newforms/fields.py:226 +msgid "Enter a valid date/time." +msgstr "Внесете правилен датум со време." + +#: newforms/fields.py:240 +msgid "Enter a valid value." +msgstr "Внесете правилна вредност." + +#: newforms/fields.py:269 core/validators.py:161 +msgid "Enter a valid e-mail address." +msgstr "Внесeте правилна адреса за е-пошта." + +#: newforms/fields.py:287 newforms/fields.py:309 +msgid "Enter a valid URL." +msgstr "Внесете правилна адреса." + +#: newforms/fields.py:311 +msgid "This URL appears to be a broken link." +msgstr "Оваа адреса изгледа дека не е достапна." + +#: newforms/fields.py:359 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "Изберете правилно. Тоа не е едно од можните избори." + +#: newforms/fields.py:377 newforms/fields.py:453 +msgid "Enter a list of values." +msgstr "Внесете листа на вредности." + +#: newforms/fields.py:386 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Внесете правилно. %s не е еден од достапните вредности." + +#: template/defaultfilters.py:436 +msgid "yes,no,maybe" +msgstr "да, не, можеби" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s беше успешно создаден." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s беше успешно ажуриран." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(verbose_name)s беше избришан." + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Оваа вредност смее да има само букви, бројки или долни црти." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Оваа вредност смее да има само букви, бројки, долни црти, црти или коси црти." + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Оваа вредност смее да содржи само букви, бројки, долни црти или црти." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Големи букви не се дозволени." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Мали букви не се дозволени." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Внесете само цифри одделени со запирки." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Внесете валидни адреси за е-пошта одделени со запирки." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Ве молам внесете валидна ИП адреса." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "Празни вредности не се дозволени." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Ненумерички знаци не се дозволени тука." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Оваа вредност не смее да биде само од цифри." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Дозволени се само букви." + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "Годината мора да биде 1900 или покасно." + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Неправилен датум: %s." + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Внесете правилно време во форматот HH:MM." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Качете валидна фотографија. Датотеката која ја качивте или не беше " +"фотографија или беше расипана датотеката." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Адресата %s не покажува кон валидна фотографија." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Телефонските броеви мора да бидат во XXX-XXX-XXXX форматот. „%s“ не е " +"валиден." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Адресата „%s“ не покажува кон QuickTime видео." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Задолжителна е правилна адреса." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Задолжителен е правилен HTML. Грешките се:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Неправилно формиран XML: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "Неправилна адреса: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Адресата %s е скршена врска." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Внесете правилна скратеница за држава во САД." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Внимавајте на јазикот. Тука не е дозволен зборот %s." +msgstr[1] "Внимавајте на јазикот. Тука не се дозволени зборовите %s." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Ова поле мора да соодејствува со полето „%s“." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Ве молам внесете нешто во барем едно поле." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Ве молам внесете во двете полиња или оставете ги двете празни." + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Ова поле мора да биде зададено ако %(field)s е %(value)s" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Ова поле мора да биде зададено ако %(field)s не е %(value)s" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "Дупликат вредности не се дозволени." + +#: core/validators.py:364 +#, python-format +msgid "This value must be between %s and %s." +msgstr "Оваа вредноста мора да биде помеѓу %s и %s." + +#: core/validators.py:366 +#, python-format +msgid "This value must be at least %s." +msgstr "Оваа вредноста мора да биде најмалку %s." + +#: core/validators.py:368 +#, python-format +msgid "This value must be no more than %s." +msgstr "Оваа вредност не смее да биде поголема од %s." + +#: core/validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "Оваа вредноста мора да биде степен од %s." + +#: core/validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "Ве молам внесете правилен децимален број." + +#: core/validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Ве молам внесете правилен децимален број со најмногу %s цифрa." +msgstr[1] "Ве молам внесете правилен децимален број со најмногу %s вкупно цифри." + +#: core/validators.py:422 +#, python-format +msgid "Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s " +"цифра." +msgstr[1] "" +"Ве молам внесете правилен децимален број кој во целиот број има најмногу %s " +"цифри." + +#: core/validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Ве молам внесете правилен децимален број кој има најмногу %s децимална цифра." +msgstr[1] "Ве молам внесете правилен децимален број кој има најмногу %s децимални цифри." + +#: core/validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Потврдете дека качената датотека има најмалку %s бајти." + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Потврдете дека качената датотека има најмногу %s бајти." + +#: core/validators.py:453 +msgid "The format for this field is wrong." +msgstr "Форматот за ова поле е грешен." + +#: core/validators.py:468 +msgid "This field is invalid." +msgstr "Ова поле не е правилно." + +#: core/validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Неможев да извадам ништо од %s." + +#: core/validators.py:507 +#, python-format +msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "Адресата %(url)s врати неправилно заглавје Content-Type „%(contenttype)s“." + +#: core/validators.py:540 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Ве молам затворете го отворениот %(tag)s таг од линијата %(line)s. (линијата " +"почнува со „%(start)s“.)" + +#: core/validators.py:544 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Некој текст кој почнува на линијата %(line)s не е дозволен во тој контекст. " +"(Линијата започнува со „%(start)s“.)" + +#: core/validators.py:549 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"„%(attr)s“ на линија %(line)s е неправилен атрибут. (линијата започнува со „%" +"(start)s“.)" + +#: core/validators.py:554 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"„<%(tag)s>“ на линија %(line)s е неправилен таг. (линијата започнува со „%" +"(start)s“.)" + +#: core/validators.py:558 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"На таг од линијата %(line)s му недостасува еден или повеќе од потребните " +"атрибути (линијата започнува со „%(start)s“)." + +#: core/validators.py:563 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "Атрибутот „%(attr)s“ на линијата %(line)s има неправилна вредност (линијата започнува со „%(start)s“)." + diff --git a/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..64971dc55ebb068818963c184807f582b0b236f3 GIT binary patch literal 1921 zcwS{v-A^1<6u*_Px@y(duNWV0Oqyt#S$2UcuGm1KRI1xH?AEr4F}ciKX9s5Hl9^eQ z#6+O6<-rGRFd+uopf$W=VU;Yh{0n#9jjtv?8519T^LJ*ZV9L$j`JHp_`S_i4_V<(R z-!hz6a9_jy4fize>M@p_!V`>j0*k;lU_bC_-~jMAa4dTt2R;M53Csa);FG{0!!Ljx zz}vuf;Mc%wz;CiVe`NN*vO51{dH>Dwl92;Ww55LCzpQ1d#=YX$wr1oCm%fLb4tH2w;=P6%DI+w39|L;4}{+0l#8Kitw_UNev^?OaxYTEZ?zf z5xcCEkI!dpTzc?g6<+35ejRs|S6D^5(v0}5st4SxDaVv=gl}@;TikRVbBeP{4WX4f z8U-j|32Z=BmI0O|D)dL8us?x8gf&h7L$}p12!_`1dqOdV$86U|Y zbd+D>)}-)lH5ul{<+KVSqa50fWt^|up;1*Op1XRbY77TbMDR97L?lbRr%-s?DD)V` zv%FX+o$WnSEEWoIG{)tO6CU;J>Ep%TQr|mg3gq{p2qU8!2tRa1q=FK^C{v>(kCiVR_FK#sa>L4xqz@Le)1Vp2&!g^XS2+H=*oFu~8NGL{I%@PE`Vret zWnf(JO=UU0UE=){PL#W0T$H{H(l$OgFD9gm{KaCvFNZ&{-qV}$t#~1x(=Dc#bxUvS zP28PCiRa@5sQd70(Z5uG2W?k3pu;VmWBMK(nlS!M5i9x^{gZAW*ACYY0S*1DZaik+ z!Y7{BI|zLAgon+85-TWr2hUXci2DYr&V%GbxbNsD)9boPY2y{97b!`6i+EAs15^ix zL&GWw@dDRNdY>$MH?^&By$G*8@*!cDge8>N!eHhpU>&LUi2&Mpm?pc2x(gt3U|56l zzxQP*%|ipz_wl|?Tv*qn;@HmflQeN%gX6AZCs0`&{1z0j@U^c^zJu7%lA% z1`0|vp%$hM%OZMe5#OC;-mQ49o2cN|q^9uQOJhJF8Nk8f)<9t!7p!=~bnn2B%o!x8 zTLfk5da~SYa^FaD(xhn~O^R=4T literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..0c06326 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/mk/LC_MESSAGES/djangojs.po @@ -0,0 +1,119 @@ +# translation of djangojs.po to Macedonian +# +# Georgi Stanojevski , 2006, 2007. +msgid "" +msgstr "" +"Project-Id-Version: djangojs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-02-15 10:53+1100\n" +"PO-Revision-Date: 2007-02-24 13:49+0100\n" +"Last-Translator: Georgi Stanojevski \n" +"Language-Team: Macedonian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Достапно %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Избери ги сите" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Додади" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Отстрани" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Избрано %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Означете го вашиот избор/и и кликнете" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Исчисти ги сите" + +#: contrib/admin/media/js/dateparse.js:32 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Јануари Февруари Март Април Мај Јуни Јули Август Септември Октомври Ноември " +"Декември" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Недела Понеделник Вторник Среда Четврток Петок Сабота" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "Н П В С Ч П С" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Now" +msgstr "Сега" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51 +msgid "Clock" +msgstr "Часовник" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78 +msgid "Choose a time" +msgstr "Избери време" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "Midnight" +msgstr "Полноќ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "6 a.m." +msgstr "6 наутро" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Noon" +msgstr "Пладне" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Cancel" +msgstr "Откажи" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177 +msgid "Today" +msgstr "Денеска" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +msgid "Calendar" +msgstr "Календар" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175 +msgid "Yesterday" +msgstr "Вчера" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Tomorrow" +msgstr "Утре" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Прикажи" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Скриј" + diff --git a/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..f4be11f7c5dd91fcf546271dc7eecabfd7ee902e GIT binary patch literal 38004 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 YmfD}=^8YR3w;#4;zudeO=ghbMA1s>QVE_OC literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 0000000..8986215 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,2277 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the django package. +# Johan C. Stöver , 2005. +# Rudolph Froger , 2006. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-12-09 15:51+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Johan C. Stöver \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: db/models/manipulators.py:305 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" +"%(object)s van het type %(type)s bestaat al voor het gegeven %(field)s." + +#: db/models/manipulators.py:306 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "en" + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "Geef een geldig %s veld." + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "Scheid meerdere ID's door komma's." + +#: db/models/fields/related.py:620 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Houd \"Control\", of \"Command\" op een Mac, ingedrukt om meerdere te " +"selecteren." + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Geef een geldig %(self)s IDs. De waarde %(value)r is ongeldig." +msgstr[1] "Geef een geldig %(self)s IDs. De waarden %(value)r zijn ongeldig." + +#: db/models/fields/__init__.py:41 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s met deze %(fieldname)s bestaat al." + +#: db/models/fields/__init__.py:115 db/models/fields/__init__.py:266 +#: db/models/fields/__init__.py:560 db/models/fields/__init__.py:571 +#: forms/__init__.py:352 newforms/fields.py:60 newforms/fields.py:288 +msgid "This field is required." +msgstr "Dit veld is verplicht." + +#: db/models/fields/__init__.py:349 +msgid "This value must be an integer." +msgstr "De waarde moet een geheel getal zijn." + +#: db/models/fields/__init__.py:381 +msgid "This value must be either True or False." +msgstr "De waarde moet of True (waar) of False (onwaar) zijn." + +#: db/models/fields/__init__.py:397 +msgid "This field cannot be null." +msgstr "Dit veld mag niet leeg zijn." + +#: db/models/fields/__init__.py:424 core/validators.py:147 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Geef een geldige datum in JJJJ-MM-DD formaat." + +#: db/models/fields/__init__.py:486 core/validators.py:156 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Geef geldige datum/tijd in JJJJ-MM-DD UU:MM formaat." + +#: db/models/fields/__init__.py:580 +msgid "Enter a valid filename." +msgstr "Geef een geldige bestandsnaam." + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Arabisch" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengaals" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "Tjechisch" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "Wels" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "Deens" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "Duits" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "Grieks" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "Engels" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "Spaans" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Argentijns Spaans" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "Fins" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "Frans" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "Galicisch" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "Hongaars" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "Hebreews" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "IJslands" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "Italiaans" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "Japans" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "Nederlands" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "Noors" + +#: conf/global_settings.py:59 +msgid "Polish" +msgstr "Pools" + +#: conf/global_settings.py:60 +msgid "Brazilian" +msgstr "Braziliaans" + +#: conf/global_settings.py:61 +msgid "Romanian" +msgstr "Roemeens" + +#: conf/global_settings.py:62 +msgid "Russian" +msgstr "Russisch" + +#: conf/global_settings.py:63 +msgid "Slovak" +msgstr "Slovaaks" + +#: conf/global_settings.py:64 +msgid "Slovenian" +msgstr "Sloveens" + +#: conf/global_settings.py:65 +msgid "Serbian" +msgstr "Servisch" + +#: conf/global_settings.py:66 +msgid "Swedish" +msgstr "Zweeds" + +#: conf/global_settings.py:67 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:68 +msgid "Turkish" +msgstr "Turks" + +#: conf/global_settings.py:69 +msgid "Ukrainian" +msgstr "Oekraïens" + +#: conf/global_settings.py:70 +msgid "Simplified Chinese" +msgstr "Vereenvoudigd Chinees" + +#: conf/global_settings.py:71 +msgid "Traditional Chinese" +msgstr "Traditioneel Chinees" + +#: core/validators.py:64 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Deze waarde mag alleen letters, getallen en liggende strepen bevatten." + +#: core/validators.py:68 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Deze waarde mag alleen letters, cijfers, liggende strepen en schuine strepen " +"bevatten." + +#: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "" +"Deze waarde mag alleen letters, cijfers, liggende strepen en verbindingsstrepen " +"bevatten." + +#: core/validators.py:76 +msgid "Uppercase letters are not allowed here." +msgstr "Hoofdletters zijn hier niet toegestaan." + +#: core/validators.py:80 +msgid "Lowercase letters are not allowed here." +msgstr "Kleine letters zijn hier niet toegestaan." + +#: core/validators.py:87 +msgid "Enter only digits separated by commas." +msgstr "Geef alleen cijfers op, gescheiden door komma's." + +#: core/validators.py:99 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Geef geldige e-mailadressen op, gescheiden door komma's." + +#: core/validators.py:103 +msgid "Please enter a valid IP address." +msgstr "Geef een geldig IP adres op." + +#: core/validators.py:107 +msgid "Empty values are not allowed here." +msgstr "Lege waarden zijn hier niet toegestaan." + +#: core/validators.py:111 +msgid "Non-numeric characters aren't allowed here." +msgstr "Niet-numerieke karakters zijn hier niet toegestaan." + +#: core/validators.py:115 +msgid "This value can't be comprised solely of digits." +msgstr "Deze waarde kan niet alleen uit cijfers bestaan." + +#: core/validators.py:120 newforms/fields.py:103 +msgid "Enter a whole number." +msgstr "Geef een geheel getal op." + +#: core/validators.py:124 +msgid "Only alphabetical characters are allowed here." +msgstr "Alleen alfabetische karakters zijn toegestaan" + +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "Het jaartal moet 1900 of nieuwer zijn." + +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s." +msgstr "Ongeldige datum: %s" + +#: core/validators.py:152 +msgid "Enter a valid time in HH:MM format." +msgstr "Geef een geldige tijd in UU:MM formaat." + +#: core/validators.py:161 newforms/fields.py:207 +msgid "Enter a valid e-mail address." +msgstr "Geef een geldig e-mailadres op." + +#: core/validators.py:173 core/validators.py:442 forms/__init__.py:667 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Er was geen bestand verstuurd. Controleer de encoding van het formulier." + +#: core/validators.py:177 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Bestand ongeldig. Het bestand dat is gegeven is geen afbeelding of was " +"beschadigd." + +#: core/validators.py:184 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "De URL %s wijst niet naar een afbeelding." + +#: core/validators.py:188 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Telefoonnummers moeten volgens het XXX-XXX-XXXX formaat zijn. \"%s\" is " +"ongeldig." + +#: core/validators.py:196 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "De URL %s wijst niet naar een QuickTime video." + +#: core/validators.py:200 +msgid "A valid URL is required." +msgstr "Een geldige URL is vereist." + +#: core/validators.py:214 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Geldige HTML is vereist. De specifieke fouten zijn:\n" +"%s" + +#: core/validators.py:221 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Foute XML: %s" + +#: core/validators.py:238 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ongeldige URL: %s" + +#: core/validators.py:243 core/validators.py:245 +#, python-format +msgid "The URL %s is a broken link." +msgstr "De URL %s is een niet werkende link." + +#: core/validators.py:251 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Geef een geldige afkorting van een VS staat." + +#: core/validators.py:265 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Pas op uw taalgebruik! Gebruik van %s niet toegestaan." +msgstr[1] "Pas op uw taalgebruik! Gebruik van de woorden %s niet toegestaan." + +#: core/validators.py:272 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Dit veld moet overeenkomen met het '%s' veld." + +#: core/validators.py:291 +msgid "Please enter something for at least one field." +msgstr "Voer tenminste één veld in." + +#: core/validators.py:300 core/validators.py:311 +msgid "Please enter both fields or leave them both empty." +msgstr "Voer waarden in in beide velden of laat beide leeg." + +#: core/validators.py:318 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Dit veld moet opgegeven worden indien %(field)s %(value)s is" + +#: core/validators.py:330 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Dit veld moet worden opgegeven indien %(field)s niet %(value)s is" + +#: core/validators.py:349 +msgid "Duplicate values are not allowed." +msgstr "Dubbele waarden zijn niet toegestaan." + +#: core/validators.py:364 +#, python-format +msgid "This value must be between %s and %s." +msgstr "De waarde moet tussen %s en %s zijn." + +#: core/validators.py:366 +#, python-format +msgid "This value must be at least %s." +msgstr "De waarde moet minimaal %s zijn." + +#: core/validators.py:368 +#, python-format +msgid "This value must be no more than %s." +msgstr "De waarde mag niet meer zijn dan %s." + +#: core/validators.py:404 +#, python-format +msgid "This value must be a power of %s." +msgstr "De waarde moet een macht van %s zijn." + +#: core/validators.py:415 +msgid "Please enter a valid decimal number." +msgstr "Geef een geldig decimaal getal." + +#: core/validators.py:419 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Geef een geldig decimaal getal met hooguit %s cijfer." +msgstr[1] "Geef een geldig decimaal getal met hooguit %s cijfers." + +#: core/validators.py:422 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Geef een geldig decimaal getal waarbij het gehele getal minimaal %s cijfer heeft." +msgstr[1] "Geef een geldig decimaal getal waarbij het gehele getal minimaal %s cijfers heeft." + +#: core/validators.py:425 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Geef een decimaal getal met hooguit %s cijfer achter de komma." +msgstr[1] "Geef een decimaal getal met hooguit %s cijfers achter de komma." + +#: core/validators.py:435 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Zorg ervoor dat het bestand minstens %s bytes groot is." + +#: core/validators.py:436 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Zorg ervoor dat het bestand hoogstens %s bytes groot is." + +#: core/validators.py:453 +msgid "The format for this field is wrong." +msgstr "Het formaat van dit veld is fout." + +#: core/validators.py:468 +msgid "This field is invalid." +msgstr "Dit veld is ongeldig." + +#: core/validators.py:504 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Kan niks ophalen van %s." + +#: core/validators.py:507 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"De geretourneerde URL %(url)s bevat een ongeldige Content-Type '%" +"(contenttype)s." + +#: core/validators.py:540 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Sluit de niet gesloten %(tag)s tag op regel %(line)s. (Regel start met \"%" +"(start)s\".)" + +#: core/validators.py:544 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Tekst beginnend op regel %(line)s is in deze context niet toegestaan. (Regel " +"start met \"%(start)s\".)" + +#: core/validators.py:549 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" op regel %(line)s is een ongeldig attribuut. (Regel start met " +"\"%(start)s\".)" + +#: core/validators.py:554 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" op regel %(line)s is een ongeldige tag. (Regel start met \"%" +"(start)s\".)" + +#: core/validators.py:558 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Een of meerdere attributen ontbreken bij een tag op regel %(line)s. (Regel " +"start met \"%(start)s\".)" + +#: core/validators.py:563 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"De \"%(attr)s\" attribuut op regel %(line)s heeft een ongeldige waarde. " +"(Regel start met \"%(start)s\".)" + +#: contrib/auth/forms.py:16 +msgid "The two password fields didn't match." +msgstr "De twee ingevulde wachtwoorden zijn niet gelijk." + +#: contrib/auth/forms.py:24 +msgid "A user with that username already exists." +msgstr "Een gebruiker met deze gebruikersnaam bestaat al." + +#: contrib/auth/forms.py:52 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Het lijkt erop dat uw browser geen cookies accepteerd. Om aan te melden " +"moeten cookies worden geaccepteerd." + +#: contrib/auth/forms.py:59 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Voer een correcte gebruikersnaam en wachtwoord in. Let op, de velden zijn " +"hoofdletter-gevoelig." + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "Dit account is inactief." + +#: contrib/auth/forms.py:84 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "Dat e-mailadres heeft geen gerelateerd gebruikersaccount. Weet u zeker dat u zich heeft geregistreerd?" + +#: contrib/auth/forms.py:116 +msgid "The two 'new password' fields didn't match." +msgstr "De twee 'nieuw wachtwoord' velden zijn niet gelijk." + +#: contrib/auth/forms.py:123 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "Uw oude wachtwoord was niet correct ingevoerd. Voert u het alstublieft opnieuw in." + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "naam" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "codenaam" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "recht" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "rechten" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "groep" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "groepen" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "gebruikersnaam" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "Verplicht. 30 tekens of minder. Alleen alfanumerieke tekens (letters, cijfers en liggende strepen)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "voornaam" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "achternaam" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "e-mailadres" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "wachtwoord" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Gebruik '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "staf status" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Bepaalt of de gebruiker kan inloggen op deze admin site." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "actief" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "Bepaalt of de gebruiker kan inloggen op deze admin site. U kunt dit uitvinken in plaats van een gebruiker te verwijderen." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "supergebruiker status" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "Bepaald dat deze gebruiker alle rechten heeft, zonder deze expliciet toe te wijzen." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "laatste aanmelding" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "datum toegetreden" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Bovenop de rechten welke handmatig zijn toegekend, krijgt deze gebruiker ook " +"alle rechten van de groepen waar hij of zij deel van uitmaakt." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "gebruikersrechten" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "gebruiker" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "gebruikers" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Persoonlijke informatie" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Rechten" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Belangrijke data" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Groepen" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "bericht" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Afmelden" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "actie tijd" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "object id" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "object repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "actie vlag" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "wijzig bericht" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "log ingave" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "log ingaves" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                  By %s:

                                                                                  \n" +"
                                                                                    \n" +msgstr "" +"

                                                                                    Door %s:

                                                                                    \n" +"
                                                                                      \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Alle" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Elke datum" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Vandaag" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Laatste 7 dagen" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Deze maand" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Dit jaar" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ja" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nee" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Onbekend" + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Aanmelden" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Uw sessie is verlopen, meldt u opnieuw aan. Maakt u geen zorgen: Uw bijdrage " +"is opgeslagen." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Het lijkt erop dat uw browser geen cookies accepteerd. Zet het gebruik van " +"cookies aan in uw browser, laad deze pagina nogmaals en probeer het opnieuw." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Gebruikersnamen mogen geen '@' bevatten." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Uw e-mailadres is niet uw gebruikersnaam. Probeer '%s' eens." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Site beheer" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:18 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "De %(name)s \"%(obj)s\" is toegevoegd." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:23 +msgid "You may edit it again below." +msgstr "U kunt dit hieronder weer bewerken." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "U kunt hieronder de volgende %s toevoegen." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Toevoegen %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "%s toegevoegd." + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Gewijzigd %s" + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "%s verwijderd." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Geen velden gewijzigd." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "Het wijzigen van %(name)s \"%(obj)s\" is geslaagd." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "De %(name)s \"%(obj)s\" was toegevoegd. U kunt het hieronder wijzigen." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Wijzig %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Een of meer %(fieldname)s in %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Een of meer %(fieldname)s in %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "De verwijdering van %(name)s \"%(obj)s\" is geslaagd." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Weet u het zeker?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Wijzigingsgeschiedenis: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Selecteer %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Selecteer %s om te wijzigen" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Database fout" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "tag:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filter:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "view:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "App %r niet gevonden" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "Model %r niet gevonden in app %r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "the related `%s.%s` object" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "model:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "related `%s.%s` objects" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "alle %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "nummer van %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Velden van %s objects" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Geheel getal" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Boolean (True of False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Karakterreeks (hooguit %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Komma-gescheiden gehele getallen" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Datum (zonder tijd)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Datum (met tijd)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "E-mailadres" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Bestandspad" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Decimaal getal" + +#: contrib/admin/views/doc.py:304 contrib/comments/models.py:85 +msgid "IP address" +msgstr "IP adres" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Boolean (True, False of None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relatie tot ouder model" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Telefoonnummer" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Tekst" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Tijd" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Staat van de VS (twee hoofdletters)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML Tekst" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s lijkt geen urlpattern object te zijn" + +#: contrib/admin/views/auth.py:29 +msgid "Add user" +msgstr "Gebruiker toevoegen" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "Alle data" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Alles tonen" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "Documentatie" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "Wachtwoord wijzigen" + +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/comments/templates/comments/form.html:6 +msgid "Log out" +msgstr "Afmelden" + +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Home" +msgstr "Voorpagina" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Verwijderen" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Het verwijderen van %(object_name)s '%(escaped_object)s' zal ook gerelateerde " +"objecten verwijderen. Echter u heeft geen rechten om de volgende typen " +"objecten te verwijderen:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" +"Weet u zeker dat u %(object_name)s \"%(escaped_object)s\" wilt verwijderen? Alle " +"volgende opjecten worden verwijderd:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Ja, Ik weet het zeker" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Pagina niet gevonden" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Onze excuses, maar de gevraagde pagina bestaat niet." + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "Toevoegen" + +#: contrib/admin/templates/admin/change_form.html:20 +#: contrib/admin/templates/admin/object_history.html:5 +msgid "History" +msgstr "Geschiedenis" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Toon op site" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Herstel de fout hieronder." +msgstr[1] "Herstel de fouten hieronder." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Sortering" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Sortering:" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Op %(filter_title)s " + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Opslaan als nieuw item" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Opslaan en nieuw item" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Opslaan en bewerk opnieuw" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Opslaan" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "%(name)s toevoegen" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Beschikbare modellen in de %(name)s toepassing." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Wijzigen" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "U heeft geen rechten om iets te wijzigen" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Recente acties" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mijn acties" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Geen beschikbaar" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django site beheer" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django beheer" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Datum/tijd" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Gebruiker" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Actie" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "d-n-Y H:i:s" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Dit object heeft geen wijzigingsgeschiedenis. Het is mogelijk niet via de " +"admin site toegevoegd." + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Server fout" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Server fout (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Server Fout (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Er is een fout opgetreden. Dit is inmiddels doorgegeven aan de sitebeheerder " +"via e-mail en zal spoedig worden gerepareerd. Bedankt voor uw geduld." + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "Er is iets mis met de database. Verzeker u ervan dat de benodigde tabellen zijn aangemaakt en dat de database toegankelijk is voor de juiste gebruiker." + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Zoek" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 resultaat" +msgstr[1] "%(counter)s resultaten" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s totaal" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filter" + +#: contrib/admin/templates/admin/login.html:17 +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +msgid "Username:" +msgstr "Gebruikersnaam:" + +#: contrib/admin/templates/admin/login.html:20 +#: contrib/comments/templates/comments/form.html:8 +msgid "Password:" +msgstr "Wachtwoord:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Welkom," + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "Vul allereerst een gebruikersnaam en wachtwoord in. Vervolgens kunt u de andere opties instellen." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Gebruikersnaam" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Wachtwoord" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Wachtwoord (nogmaals)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Vul hetzelfde wachtwoord als hierboven in, ter bevestiging." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Documentatie bookmarklets" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                      To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                      \n" +msgstr "" +"\n" +"

                                                                                      Om bookmarklets te installeren, sleep de link naar uw " +"bladwijzers\n" +"werkbalk, of rechtermuis klik op de link en voeg het toe aan de bladwijzer. " +"Nu kan\n" +"de bookmarklet vanuit elke pagina op de site worden gekozen. Let erop dat " +"het soms\n" +"noodzakelijk is dat de computer van waaruit de pagina wordt bekeken intern " +"is\n" +"(Raadpleeg uw systeembeheerder of uw computer zich op het interne netwerk " +"bevind).

                                                                                      \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentatie voor deze pagina" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Spring vanuit elke pagina naar de documentatie voor de view die gegenereerd " +"wordt door die pagina" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Toon object ID" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Toont de content-type en unieke ID voor pagina's die een enkel object " +"voorsteld." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Bewerk dit object (huidig venster)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Ga naar de beheerpagina voor pagina's die een enkel object weergeven." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Bewerk dit object (nieuwe pagina)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Zoals hierboven, maar opent de beheerpagina in een nieuw venster." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Datum:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Tijd:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Huidige:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Wijziging:" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Wachtwoord hersteld" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Uw wachtwoord vergeten? Geef uw e-mailadres op en er zal een nieuw " +"wachtwoord worden toegekend en aan u worden toegezonden." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-mailadres:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Herstel mijn wachtwoord" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "U krijgt deze e-mail omdat u om een nieuw wachtwoord heeft gevraagd" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "voor uw gebruikersaccount op %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Uw nieuwe wachtwoord is: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Aarzel niet om op deze pagina uw wachtwoord te wijzigen:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Uw gebruikersnaam, mocht u deze vergeten zijn:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Bedankt voor het gebruik van onze site!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Het %(site_name)s team" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Bedankt voor de aanwezigheid op de site vandaag." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Meld u opnieuw aan" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Wachtwoord herstel geslaagd" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Een nieuw wachtwoord is per e-mail verstuurd. U zult het spoedig ontvangen." + +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "Wachtwoord wijziging" + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Vanwege de beveiliging moet u uw oude en twee keer een nieuw " +"wachtwoordinvoeren, zodat we kunnen controleren of er geen typefouten zijn " +"gemaakt." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Oud wachtwoord:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nieuw wachtwoord:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Bevestig wachtwoord:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Wijzig mijn wachtwoord" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Wachtwoord wijzigen is geslaagd" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Uw wachtwoord is gewijzigd." + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domeinnaam" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "weergavenaam" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "site" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sites" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Voorbeeld: '/about/contact/'. Zorg voor slashes aan het begin en eind." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titel" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "inhoud" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "opmerkingen toestaan" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "sjabloonnaam" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Voorbeeld: 'flatpages/contact_page'. Als deze niet is opgegeven, dan wordt " +"'flatpages/default.html' gebruikt." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "registratie verplicht" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Indien dit is aangekruist kunnen alleen ingelogde gebruikers deze pagina " +"bekijken." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "platte pagina" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "platte pagina's" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "omgeleid via" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Dit moet een absoluut pad zijn, zonder de domein naam. Bijvoorbeeld: '/" +"events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "omleiden naar" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Dit kan een absoluut pad (zoals hierboven) zijn of een volledige URL " +"beginnend met 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "omleiding" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "omleidingen" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "object ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "kop" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "opmerking" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "waardering #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "waardering #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "waardering #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "waardering #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "waardering #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "waardering #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "waardering #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "waardering #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "is een geldige waardering" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "datum/tijd toegevoegd" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "is openbaar" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "is verwijderd" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Kruis deze box aan indien de opmerking niet gepast is. Een \"Dit commentaar " +"is verwijderd\" bericht wordt dan getoond" + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "opmerkingen" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Inhoud object" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Gepost door %(user)s op %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "naam van persoon" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "ip adres" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "goedgekeurd door de staf" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "vrije opmerking" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "vrije opmerkingen" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "score" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "score datum" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "karma score" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "karma scores" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d waardering door %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Deze opmerking is gemarkeerd door %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "markeerdatum" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "gebruikersmarkering" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "gebruikersmarkeringen" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Gemarkeerd door %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "datum verwijdering" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "verwijderd door moderator" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "verwijderd door moderator" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Verwijderd door moderator %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonieme gebruikers kunnen niet stemmen" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Ongeldige opmerkingen ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Niet op uzelf stemmen" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"Deze waardering is verplicht omdat u tenminste één andere waardering hebt " +"ingevoerd." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Deze opmerking is gepost door een gebruiker die minder dan %(count)s " +"opmerking heeft gepost:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Deze opmerking is gepost door een gebruiker die minder dan %(count)s " +"opmerkingen heeft gepost:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Deze opmerking is gepost door een \"fijne\" gebruiker:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Alleen POSTs zijn toegestaan" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Een of meerdere verplichte velden zijn niet ingevuld" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Iemand heeft het opmerkingenformulier gewijzigd (Beveilingsinbreuk)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"Het opmerkingenformulier heeft een ongeldig 'target' parameter -- het object " +"ID was ongeldig" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Het opmerkingenformulier heeft geen 'voorbeeld' of 'post'" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Uw wachtwoord vergeten?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Waarderingen" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Verplicht" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Optioneel" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Plaats een foto" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Opmerking:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Concept opmerking" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Uw gebruikersnaam:" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "sessiesleutel" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "sessiegegevens" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "verloopdatum" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sessie" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sessies" + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "python model-class-naam" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "inhoudstype" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "inhoudstypen" + +#: forms/__init__.py:387 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Zorg ervoor dat uw tekst korter is dan %s karakter." +msgstr[1] "Zorg ervoor dat uw tekst korter is dan %s karakters." + +#: forms/__init__.py:392 +msgid "Line breaks are not allowed here." +msgstr "Regeleindes zijn niet toegestaan." + +#: forms/__init__.py:493 forms/__init__.py:566 forms/__init__.py:605 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Selecteer een geldige keuze; '%(data)s is niet in %(choices)s." + +#: forms/__init__.py:669 +msgid "The submitted file is empty." +msgstr "Het gegeven bestand is leeg." + +#: forms/__init__.py:725 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Geef een geheel getal op tussen -32.768 en 32.767." + +#: forms/__init__.py:735 +msgid "Enter a positive number." +msgstr "Geef een geheel getal op." + +#: forms/__init__.py:745 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Geef een geheel getal op tussen 0 en 32.767." + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "De %(verbose_name)s is succesvol aangemaakt." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "De %(verbose_name)s is succesvol aangepast." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "De %(verbose_name)s is verwijderd." + +#: utils/dates.py:6 +msgid "Monday" +msgstr "maandag" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "dinsdag" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "woensdag" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "donderdag" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "vrijdag" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "zaterdag" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "zondag" + +#: utils/dates.py:14 +msgid "January" +msgstr "januari" + +#: utils/dates.py:14 +msgid "February" +msgstr "februari" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "maart" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "april" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "mei" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "juni" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "juli" + +#: utils/dates.py:15 +msgid "August" +msgstr "augustus" + +#: utils/dates.py:15 +msgid "September" +msgstr "september" + +#: utils/dates.py:15 +msgid "October" +msgstr "oktober" + +#: utils/dates.py:15 +msgid "November" +msgstr "november" + +#: utils/dates.py:16 +msgid "December" +msgstr "december" + +#: utils/dates.py:19 +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "apr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mei" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "aug" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sep" + +#: utils/dates.py:20 +msgid "oct" +msgstr "okt" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dec" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "aug." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "dec." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "jaar" +msgstr[1] "jaren" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "maand" +msgstr[1] "maanden" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "week" +msgstr[1] "weken" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dag" +msgstr[1] "dagen" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "uur" +msgstr[1] "uren" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuut" +msgstr[1] "minuten" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "j-n-Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "j-n-Y H:i" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "H:i" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "j F" + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "ja,nee,misschien" + +#: newforms/fields.py:82 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "Zorg ervoor de waarde korter is dan %d tekens." + +#: newforms/fields.py:84 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "Zorg ervoor dat uw tekst langer is dan %d tekens." + +#: newforms/fields.py:135 +msgid "Enter a valid date." +msgstr "Geef een geldige datum op." + +#: newforms/fields.py:171 +msgid "Enter a valid date/time." +msgstr "Geef een geldige datum/tijd op." + +#: newforms/fields.py:184 +msgid "Enter a valid value." +msgstr "Geef een geldige waarde." + +#: newforms/fields.py:225 newforms/fields.py:245 +msgid "Enter a valid URL." +msgstr "Geef een geldige URL op." + +#: newforms/fields.py:247 +msgid "This URL appears to be a broken link." +msgstr "Deze URL schijnt niet te werken." + +#: newforms/fields.py:276 newforms/fields.py:301 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "Selecteer een geldige keuze. %s is niet onderdeel van de beschikbare keuzes ." + +#: newforms/fields.py:292 +msgid "Enter a list of values." +msgstr "Geef een lijst op met waardes." diff --git a/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/nl/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..6b3dfaf035f77891483da6472b9d183e5635831a GIT binary patch literal 1507 zcwSYKJ!~X37=}%<3)~*R;YUIOWC~u`~7vPcAw#U5#wcy zA2D9Rxc3Mvzdyl8!N0&o@Ne)5@E`DT@XA8iUjm;5UjiH88{idiwc-X?2e-fv!M%kL z|2bF#zg!6UzpCh^liG^A|kI(J<@NeSt`?LD~pQ~d1 znXXp$vwm9RVtKmE;xuLPTu3F374g?|wkZ_pR9F}~A;mVw#^l5WcXCP({#3b^56#yp-`sbHV;JB21p>YT?#R^*NkXy#~oOqTZ&XFT`K zJS?;+DGk<@sg~K;v3+ASrZd*3DZJQ#clnSXVr=mN8<3*J@pDsHo{Wu5=*|2Z7dqvM zlF5X#L4o6;CsSfrQ0{q1sSeg~T-egsveIK9Tw!6bp$YTUSc~d+DR)$E99VOTn$b=g zjmYNG=oUYSH+SRh4j*;;gPq=fqfawqUDVApnMSvZERTk!#Ty5`VYF#UI0Qv);i$z| zo6WVT`A)R5##i2Ly?x_)6Dx3x`ZSmMrQh97+>g4ws2%eUdadrx^`qW?r?Dq;7Y(h@ zxf0IU7Vj6SQL`~lV=`RlhCL~bTj<^*&$3oG5u~$1WE2faOj}$lzOlD=OUY$pvzv|X zPPcP;u$ARzW7B9y8g;Qon~A7d9vNe&J4{^-DkB&k5P6qZ5-#owfM- zv2+cHuqZ69l;(yq((E?n$yiQ~g)OzXTWS$+A8TDGvhdiIlyidaoIFj#AB0C!Ar#xD ziMa@pjH@+1Cwm6Z5Ub3DotEO1{3M7|a=2N?MJdiHNq$;rKPkyYDb6D2`2%^HNsy=h zPoC*R@, 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 17:39+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Rudolph Froger \n" +"Language-Team: nl \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/calendar.js:24 +#: contrib/admin/media/js/dateparse.js:32 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"januari februari maart april mei juni juli augustus september oktober " +"november december" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "Z M D W D V Z" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "zondag maandag dinsdag woensdag donderdag vrijdag zaterdag" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Beschikbare %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Kies allemaal" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Toevoegen" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Verwijderen" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Gekozen %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Selecteer uw keuze(s) en klik " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Allemaal verwijderen" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Nu" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Klok" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Kies een tijd" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Middernacht" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 uur" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "12 uur" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Annuleren" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Vandaag" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalender" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Gisteren" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Morgen" diff --git a/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..ee2152b5e1610fb85833a5b8dfaf8d3439cbb732 GIT binary patch literal 27469 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.po new file mode 100644 index 0000000..427879a --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/django.po @@ -0,0 +1,2002 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2005 and beyond +# This file is distributed under the same license as the PACKAGE package. +# Espen Grindhaug , Nov 2005. +# +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:12+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Espen Grndhaug \n" +"Language-Team: Norwegian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +#, fuzzy +msgid "object ID" +msgstr "Vis objekt ID" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +#, fuzzy +msgid "comment" +msgstr "innhold" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "IP adresse" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "innhold" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +#, fuzzy +msgid "Content object" +msgstr "innholds type" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" + +#: contrib/comments/models.py:168 +#, fuzzy +msgid "person's name" +msgstr "fornavn" + +#: contrib/comments/models.py:171 +#, fuzzy +msgid "ip address" +msgstr "IP adresse" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "tillat kommentarer" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "tillat kommentarer" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "" + +#: contrib/comments/models.py:234 +#, fuzzy +msgid "score date" +msgstr "utløpsdato" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, fuzzy, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Denne kommentaren er skrevet med lite omtanke:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +#, fuzzy +msgid "flag date" +msgstr "flatside" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "Bruker" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "Brukere" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "" + +#: contrib/comments/models.py:278 +#, fuzzy +msgid "deletion date" +msgstr "sesjon data" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonyme brukere kan ikke stemme" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Ikke gyldig kommentar ID" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Du kan ikke stemme selv" + +#: contrib/comments/views/comments.py:28 +#, fuzzy +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Denne bla bla.." + +#: contrib/comments/views/comments.py:112 +#, fuzzy, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Denne kommentaren var skrevet av en bruker som har fra før skrevet under %" +"(count)s kommentarer:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Denne kommentaren var skrevet av en bruker som har fra før skrevet under %" +"(count)s kommentarer:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, fuzzy, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Denne kommentaren er skrevet med lite omtanke:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Bare POST er tillatt" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "En eller flere av feltene som er påkrevd ble ikke sendt." + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Noen har endret på komentar feltene (sikkerhets advarsel)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "Skjemaet hadde en ugyldig verdi - objekt IDen var ugyldig" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" +"Kommentar skjemaet returnerte ikke et 'forhåndsvisning' eller 'post' objekt" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Brukernavn:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Passord:" + +#: contrib/comments/templates/comments/form.html:6 +#, fuzzy +msgid "Forgotten your password?" +msgstr "Endre passord" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Log ut" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +#, fuzzy +msgid "Comment:" +msgstr "tillat kommentarer" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +#, fuzzy +msgid "Preview comment" +msgstr "tillat kommentarer" + +#: contrib/comments/templates/comments/freeform.html:4 +#, fuzzy +msgid "Your name:" +msgstr "brukernavn" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                      By %s:

                                                                                      \n" +"
                                                                                        \n" +msgstr "" +"

                                                                                        Av %s:

                                                                                        \n" +"
                                                                                          \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Alle" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Når som helst" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "I dag" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Siste 7 dager" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Denne måneden" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "I år" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Ja" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nei" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Ukjent" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "tid for handling" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "objekt id" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "objekt repr" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "handlings flagg" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "endre melding" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "logg notis" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "logg innlegg" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Alle datoer" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Vær snill å angi korrekt brukernavn og passord. La merke til at små og " +"store bokstaver er betraktet ulik." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Logg inn" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Du må logge inn igjen, fordi sesjonen din har gått ut på dato, men ikke ikke " +"bekjymr deg informasjonen du sendte ble lagret." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Det ser ut som om nettleseren din ikke vill ta i mot informasjonskapsler " +"('cookies'). Vennligst omkonfigurer nettleseren din, last siden på ny og " +"prøv igjen." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Brukernavnet kan ikke inneholde '@'" + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "E-post adressen din er ikke brukernavnet ditt, prøv '%s' i stede." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Nettsted administrasjon" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" ble lagt inn i databasen." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Du kan endre under" + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Du kan legge til en ny %s under." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Ny %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Lagt til %s" + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "og" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Endret %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Slettet %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Ingen felt endret." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" ble endret." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" ble endret. Du kan endre det igjen under." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Endre %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "En eller flere %(fieldname)s i %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "En eller flere %(fieldname)s i %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" ble slettet." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Er du sikker?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Endre historien: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Velg %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Velg %s for å endre" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Heltall" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Boolean (Enten \"True\" eller \"False\")" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Tekst (opp til %(maxlength)s tegn)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Heltall skilt med kommaer" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Dato (uten tid)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Dato/tid" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "E-post adresse" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Sti til fil" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Desimal tall" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Boolean (enten \"True\", \"False\" eller \"None\")" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Relasjon til forelder modell" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Telefonnummer" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Tekst" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Tid" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Stat (i USA, to store bokstaver)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "XML tekst" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dokumentasjon" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Endre passord" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Hjem" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Historie" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Dato/tid" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Bruker" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Funksjon" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. M U - h:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Dette objektet har ingen endrings historie. Den var sannsynligvis ikke laget " +"via denne siden" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django administrasjonsside" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django administrasjon" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Tjener feil" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Tjener feil (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Tjener feil (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Det har vært en feil. Feilen er blitt rapportert til administrator via e-" +"mail, og vill bli fikset snart. Takk for din tålmodighet." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Fant ikke siden" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Beklager, men siden du spør etter finnes ikke." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modeller fra applikasjonen %(name)s." + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Legg til" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Endre" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Du har ikke rettigheter til å endre." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Siste handlinger" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Mine handlinger" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Ingen tilgjengelige" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Legg til %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Har du glemt passordet ditt?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Velkommen" + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Slett" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Hivs du sletter %(object_name)s '%(object)s' vil du også slette relaterte " +"objekter, men du har ikke tillatelse til å slette de følgende objektene:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Er du sikker på at du vill slette %(object_name)s \"%(object)s\"? Alle de " +"følgende relaterte objektene vill bli slettet:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Ja, jeg er sikker" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "Av %(title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Gå" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Vis på nettsted" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Vennligst fiks feilen under." +msgstr[1] "Vennligst fiks feilene under." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Rekkefølge" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Rekkefølge:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Lagre som ny" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Lagre og legg til en ny" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Lagre og fortsett å endre" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Lagre" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Endre passord" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Passordet er endret" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Ditt passord er endret." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Tilbakestill passord" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Har du glemt passordet ditt? Skriv inn e-post adressen din under, så sender " +"vi deg et nytt passord via e-post." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "E-post adresse:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Tilbakestill mitt passord" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Takk for å bruke tid på internett siden i dag." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Logg inn igjen" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Passordet ble tilbakestilt" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Vi sender deg et nytt passord til e-post adressen du oppgav. Du villmotta det " +"snart." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Venligst skriv inn ditt gamle passord, for sikkerthets grunner, så skriv inn " +"ditt nye passord to ganger slik at vi kan kontrollere at det er rett." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Gammelt passord:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nytt passord:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Gjenta nytt passord:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Endre passord" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "" +"Du har mottatt denne e-posten fordi du ba om å tilbakestille passordet ditt" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "for din konto hos %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Ditt nye passord er: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Du kan endre dette passordet ved å gå til denne siden:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "I tilfellet du har glemt brukernavnet ditt, så er det:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Takk for at du bruker vår side!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Hilsen %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Bokmerker" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Dokumentasjon bokmerker" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                          To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                          \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dokumentasjon for denne siden" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Hopp fra hvilken som helst side til dokumentasjonen for visnings funksjonen " +"som laget siden." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Vis objekt ID" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Viser \"content-type\" og en unik ID for sider som representerer et enkelt " +"objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Endre dette objektet (åpnes i dette vinduet)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Hopp til administrasjonsiden for sidene som representerer et enkelt objekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Endre dette objektet (åpnes i et nytt vindu)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Samme som over, men åpner administrasjonsiden i et nytt vindu." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Dato:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Tid:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Nå:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Endre:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "omadresser fra" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Denne burde vær en fullstendig sti, uten domene navnet. Foreksempel: '/" +"nyheter/les/" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "omadresser til" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Denne kan enten være en fullstendig sti (som over), eller en hel " +"internettadresse som starter med 'http://'" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "omadressering" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "omadresserelser" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Eksempel: '/om/kontakt/'. Vær sikker på at du har en skråstrek forran og bak." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "tittel" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "innhold" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "tillat kommentarer" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "mal navn" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Eksempel: 'flatfiler/kontakt_side'. Hvis denne ikke denne er gitt, vill " +"'flatfiles/default' bli brukt." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "registrering kreves" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "" +"Hvis denne er krysset av er det bare brukere som er logget inn som kan se " +"siden." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "flatside" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "flatsider" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "navn" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "kodenavn" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "rettighet" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "rettigheter" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "gruppe" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "grupper" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "brukernavn" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "fornavn" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "etternavn" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "e-post adresse" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "passord" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "administrasjons status" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Bestemmer om brukeren kan logge inn på dette administrasjons sted." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "aktiv" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "super bruker" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "siste logg inn" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "registrerings dato" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"I tillegg til rettighetene som blir gitt manuelt, får også brukeren full " +"tilgang til gruppene han/hun er i." + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "Rettigheter" + +#: contrib/auth/models.py:70 +msgid "user" +msgstr "bruker" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "brukere" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Personlig informasjon" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Rettigheter" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Viktige datoer" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grupper" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "Melding" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "python modell klasse navn" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "innholds type" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "innholds typer" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "sesjon nøkkel" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "sesjon data" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "utløpsdato" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "sesjon" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sesjoner" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "domene navn" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "vise navn" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "nettsted" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "nettsteder" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "j. M Y" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "j. M Y - h:i" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "h:i" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Mandag" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Tirsdag" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Onsdag" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Torsdag" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Fredag" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Lørdag" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Søndag" + +#: utils/dates.py:14 +msgid "January" +msgstr "Januar" + +#: utils/dates.py:14 +msgid "February" +msgstr "Februar" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Mars" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "April" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Mai" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Juni" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Juli" + +#: utils/dates.py:15 +msgid "August" +msgstr "August" + +#: utils/dates.py:15 +msgid "September" +msgstr "September" + +#: utils/dates.py:15 +msgid "October" +msgstr "Oktober" + +#: utils/dates.py:15 +msgid "November" +msgstr "November" + +#: utils/dates.py:16 +msgid "December" +msgstr "Desember" + +#: utils/dates.py:19 +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "feb" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "apr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "aug" + +#: utils/dates.py:20 +msgid "sep" +msgstr "sep" + +#: utils/dates.py:20 +msgid "oct" +msgstr "okt" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "des" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Feb." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Aug." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Sept." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Okt." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Des." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "år" +msgstr[1] "år" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "måned" +msgstr[1] "måndeder" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "uke" +msgstr[1] "uker" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dag" +msgstr[1] "dager" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "time" +msgstr[1] "timer" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minutt" +msgstr[1] "minutter" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengalsk" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Tsjekkisk" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Walisisk" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Dansk" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Tysk" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "Gresk" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Engelsk" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Spansk" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Fransk" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galisisk" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "Ungarsk" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "Hebraiske" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Islandsk" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Italiensk" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japansk" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Nederlandsk" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norsk" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brasiliansk" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Rumensk" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Russisk" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Slovakisk" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "Slovensk" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Serbisk" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Svensk" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "Ukrainsk" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Simplifisert Kinesisk" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Tradisjonell Kinesisk" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Dette feltet må bare inneholde bokstaver, nummer og understreker." + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Dette feltet må bare inneholde bokstaver, nummer, understreker og " +"skråstreker." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Stor bokstaver er ikke tillatt her." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Små bokstaver er ikke tillatt her." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Skriv inn bare tall, skilt med kommaer." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Skriv inn e-post adresser skilt med kommaer." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Vennligst skriv inn en godkjent IP adresse." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Dette felte kan ikke være tomt." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Det er bare tall som kan stå i dette feltet." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Dette feltet kan ikke bare bestå av nummer." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Skriv inn et helt nummer." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Bare alfabetiske bokstaver er tillatt her." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Skriv inn en dato i ÅÅÅÅ-MM-DD format." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Skriv inn tiden i TT:MM format." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Skriv inn dato og tid i ÅÅÅÅ-MM-DD TT:MM format." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Skriv inn en godkjent e-post adresse." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Lastopp et bilde. Filen du lastet opp var ikke et bilde, eller så var det et " +"ødelagt bilde" + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Internettadressen %s peker ikke til et godkjent bilde." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Telefon nummeret må være i XXX-XXX-XXXX format. \"%s\" er ikke godkjent." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Internettadressen %s peker ikke til en godkjent QuickTime film." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "En godkjent internettadresse er påkrevd." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Godkjent HTML er påkrevd. Feilene var:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Ikke godkjent XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "Ikke godkjent URL: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Internettadresse fører til en side som ikke virker." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Skriv inn en godkjent amerikansk delstat forkortelse." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Pass munnen din! Ordet %s er ikke tillatt her." +msgstr[1] "Pass munnen din! Ordene %s er ikke tillatt her." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Dette feltet må være det samme som i '%s' feltet." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Vennligst skriv inn noe i minst et felt." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Vennligst skriv inn noe i begge felta, eller la dem stå blanke." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Dette feltet må bare brukes hvis %(field)s er lik %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Dette feltet må bare brukes hvis %(field)s ikke er lik %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Like verdier er ikke tillatt." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Denne verdien må være 'power' av %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Vennligst skriv inn et godkjent desimal tall." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Skriv inn et desimal tall med maksimum %s total antall tall." +msgstr[1] "Skriv inn et desimal tall med maksimum %s total antall tall." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Skriv inn et desimal tall med maksimum %s tall bak komma. " +msgstr[1] "Skriv inn et desimal tall med maksimum %s tall bak komma. " + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "" +"Vær sikker på at fila du prøver å laste opp er minimum %s bytes stor." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "" +"Vær sikker på at fila du prøver å laste opp er maksimum %s bytes stor." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Formatet i dette feltet er feil." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Dette feltet er feil." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Klarte ikke å motta noe fra %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"Internettadressen %(url)s returnerte en ikke godkjent Content-Type '%" +"(contenttype)s'." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Vennligst lukk taggen %(tag)s på linje %(line)s. (Linjen starer med \"%(start)" +"s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Noe av teksten som starter på linje %(line)s er ikke tillatt. (Linjen starter " +"med \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" på linje %(line)s er ikke en godkjent tillegg. (Linjen starter " +"med \"%(start)s\".)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" på linje %(line)s er ikke en godkjent tag. (linjen starter med " +"\"%(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"En tag på linje %(line)s mangler en av de påkrevde attributtene. (linjen starter " +"med \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"\"%(attr)s\" tillegg på linje $(line)s har en ikke godkjent verdi. (Linjen " +"starter med \"%(start)s\".)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s med %(type)s finnes allerede for angitt %(field)s." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "$(optname)s med %(fieldname)s finnes allerede." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Dette feltet er påkrevd." + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "Denne verdien må være et heltall." + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "Denne verdien må være enten \"True\" eller \"False\"." + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "Dette feltet kan ikke være null/tom." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Skriv inn et godkjent fil navn." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Vennligst skriv inn en/et gyldig %s." + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "Separer Id-ene med kommaer." + +#: db/models/fields/related.py:581 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +"Hold nede \"Control\", eller \"Command\" på en Mac, for å velge mere enn en." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Skriv inn gyldige %(self)s ID-er. Verdien %(value)r er ikke gyldig." +msgstr[1] "Skriv inn gyldige %(self)s ID-er. Verdiene %(value)r er ikke gyldige." + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Sjekk at teksten er kortere enn %s bokstav" +msgstr[1] "Sjekk at teksten er kortere enn %s bokstaver" + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Det er ikke tillatt med flere linjer her." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Velg et gyldig valg; '%(data)s' er ikke i %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "Filen er tom." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Skriv inn et heltall mellom -31768 og 32767." + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Skriv inn et positivt heltall." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Skriv inn et heltall mellom 0 og 32767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "ja,nei,kanskje" + +#, fuzzy +#~ msgid "Comments" +#~ msgstr "tillat kommentarer" + +#~ msgid "String (up to 50)" +#~ msgstr "Tekst (opp til 50 tegn)" + +#~ msgid "label" +#~ msgstr "merkelapp" + +#~ msgid "package" +#~ msgstr "pakke" + +#~ msgid "packages" +#~ msgstr "pakker" + +#~ msgid "Error in Template" +#~ msgstr "Feil i mal" + +#~ msgid "" +#~ "\n" +#~ "In template %(name)s, error at line %(line)s:\n" +#~ msgstr "" +#~ "\n" +#~ "I mal %(name)s, var det en feil på linje %(line)s:\n" diff --git a/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/no/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..4b23aba4e0013b2c5fce91d32d64172297a67f4a GIT binary patch literal 1492 zcwTLi&yO256vy2rfj0aqrO*o!=CD#rH6dAbt0m<}!)|t)?j~6>!&b`y&eU=LYC#>gu2 z2>CJc({TRBu>W(&|7*zid&u_yNs*63+!N#`@=V3&ZC8ArTa{1`xq-dM!T%K=!XxAx zzzmOy3-(h{T=Gm$FzIVfVujc#y2)Ty* zEcm}fUN8Cyalh1jAHUUnod?L`mtP>hkK%iY_?RN!%f$Cpe6Iyw3M}qjC;nYtB>p~? z;45l9i{m2kbzTkomjllRk{gsYW)0HGGSXQvA=y|0U9)71Nl+OxXs8q<*_vo=fimjE z42qQnRmMqRhBAo}Qm0dLTVz1)FqJd2q&qM+rO{_5ozTwQ2#NNR-pLiy;Q5wtZ!-MKw*vL9Gh_Pg?oE0nNTe#AO>^gTqpT`@YdkI7jV*875I83SW0|1, 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Espen Grindhaug \n" +"Language-Team: no\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s er tilgjengelige" + +#: contrib/admin/media/js/SelectFilter2.js:41 +#, fuzzy +msgid "Choose all" +msgstr "Velg alle" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Ny" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Slett" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s er valgt" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Velg ditt svaralternativ(er) og klikk" + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Tøm" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "Januar Februar Mars April Mai Juni Juli August September Oktober November Desember" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Søndag Mandag Tirsdag Onsdag Torsdag Fredag Lørdag" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "S M T O T F L" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:34 +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:72 +msgid "Show" +msgstr "Vis" + +#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63 +msgid "Hide" +msgstr "Skjul" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Nå" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Klokke" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Velg et klokkeslett" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "24.00" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "06.00" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "12.00" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Avbryt" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "I dag" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalender" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "I går" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "I morgen" diff --git a/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d132fc114e5991ec0a0367fb11f12ab94e04e4bb GIT binary patch literal 28373 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Ž_rH9piRwvraARL0q|4!iCiR2P6 zvTNwpBV8}^@B-48cJExpPrDM`rVj)KKm)!HdH7Hvkuz!nXz zzVh>ZUe6Sq18&(~^F$zuXeJbQ)C!rz%R4R|VMe6e5P(Ji%%p2?4&5$v!OJUw7#xYO zG1eu4kj&*33kS-@59KfK9aBTc;sm8)VhUx!l#yEwvBg{R=nr;6!Rd-2p8ewQtfN;e z(Ar|qW)cmsMjzJR%x=RvCqs+x#!h4fs_a}%AMw3gXI*wper{2D_pVPvuD;-|B9};k z^{Ki#FR`Jn-hJJjC6E!gF!4wq^{8To+p-%^5^ik&42qhIeYb2EAFYlAq9|MJ z?m2ES6G3IQE0AGU08NYg<^SS%j6`-H{uN+HkeCA?Hn&@}*s`v>CeZ?j_<&rKIQ)}V^=$}D%G^l*J# z>2vlS6uLk%CasKXLYL8lh4*7qHp~F7FK3cJ7yQY`H#!mN8f8XTJbPUJ9~rPl7|plw zPWneEoN=3?rx#uiBOeyNj|1#8)2?1LTVqY`!oi3Y`TE3i;iWkQTig|NooVH)r_9I7 z0{0@#B={tk>{i;X%v)7ytIX=^47JGqI>;SRzS$kB)N8xM7i&5}dp`@m{I`OX6Cx}n zil-_`M1Lo2?OMMN9G<@k(0_#=b<_zj3ha|Cd8ex3O;sM&AM~pjXHYX??wCQMXTJ$# zv&Dqai7QQZ<{PxH++6G3`Nm#+G3i#47TCi0rtM~%4Sy}DJl}KI}`tnB}7q;=IZ1NOjpBB0Vrm<8Iyi@9SN@eoQ#A4#sVv7fj1;eMz zx!!&$OJEsydQ*s?Or@^`Xde4UANv-dl!t*ow7jVqKb8 zpC&e>i7V2?#x$`hO>8z+X@Kn-Ykjb4F;Tjav7_Mc>v6I zj#Mbi2C9^^EbUi<2DpT2`Rgg&E47=Z(-x;D@XT&Yh9KObyuYG2i`(u@AQ9-Kl{6O4 ym>eUhT*|YCE%LjI^4D_R6}h4Emxq{21xC^WM?h$Z=@qz%Nua, 2006. +# cayco , 2006. +# +# +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:13+0200\n" +"PO-Revision-Date: 2006-02-21 11:10+0100\n" +"Last-Translator: Piotr Maliński \n" +"Language-Team: Polish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID obiektu" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "nagłówek" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "komentarz" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "ocena #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "ocena #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "ocena #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "ocena #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "ocena #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "ocena #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "ocena #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "ocena #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "jest poprawną oceną" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/czas dodania" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "publicznie dostepny" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "Adres IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "usunięty" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Zaznacz to pole jeżeli komentarz jest nieodpowiedni. Wyświetlony zostanie tekst \"Ten " +"komentarz został usunięty\". " + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "komentarze" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Obiekt Treści" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Dodane przez %(user)s dnia %(date)s\n" +"\n" +"%(comment)y\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "Nazwa osoby" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "adres ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "zaakceptowano" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "wolny komentarz" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "wolne komentarze" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "ilość punktów" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data przyznania punktów" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "ilość punktów" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "wyniki" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "%(score)d ocenę przez %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"Komentarz oflagowany przez %(user)s:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "data flagi" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "flaga użytkownika" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "flagi użytkownika" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Flaga %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data skasowania" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "usunięcie moderatora" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "usunięcia moderatorów" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Usunięcie moderatora przez %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Anonimowi użytkownicy nie mogą głosować" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "Błędny ID komentarza" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Nie można głosować na siebie" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"Ta ocena jest wymagana gdyż podałeś przynajmniej jedną inną ocenę." + + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Ten komentarze został dodany przez użytkownika::\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Dozwolone tylko POSTy" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Jedno lub więcej wymaganych pól nie zostało wypełnionych" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Ktoś próbował obejść zabezpieczenia formularza komentarzy" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "Formularz komentarza miał niepoprawny parametr 'target' -- ID obiektu było " +"niepoprawne" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "Formularz komentarza nie zapewnił obiektów 'preview' ani 'post'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Nazwa użytkownika:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Hasło:" + +#: contrib/comments/templates/comments/form.html:6 +msgid "Forgotten your password?" +msgstr "Zapomniałeś hasło?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Wyloguj się" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Oceny" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Wymagane" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcjonalne" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Wyślij zdjęcie" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Komentarz:" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +msgid "Preview comment" +msgstr "Podgląd" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "Twoje imię:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                          By %s:

                                                                                          \n" +"
                                                                                            \n" +msgstr "" +"

                                                                                            Przez %s:

                                                                                            \n" +"
                                                                                          \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Wszystko" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Jakolwiek data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Dzisiaj" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Ostatnie 7 dni" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Ten miesiąc" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Ten rok" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Tak" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Nie" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Nieznany" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "czas akcji" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id obiektu" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "reprezentacj obiektu" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "flaga akcji" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "zmień wiadomość" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "log" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "logi" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Wszystkie daty" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma " +"znaczenie." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Zaloguj się" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Zaloguj się ponownie. Twoja sesja wygasła lecz twoje zgłoszenie " +"zostało zapisane." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień " +"jej ustawienia i spróbuj ponownie." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Nazwy użytkowników nie mogą zawierać znaków '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Twój adres e-mail to nie jest twój login. Spróbuj '%s'." + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Administracja stroną" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" dodany pomyślnie." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Możesz ponownie edytować wpis poniżej." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Możesz dodać nowy wpis %s poniżej." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Dodaj %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Dodano %s" + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "i" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Zmieniono %s" + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Skasowano %s" + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Żadne pole nie zmienione." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej." + + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Zmień %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Jedno lub więcej %(fieldname)s w %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Jedno lub więcej %(fieldname)s w %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Jesteś pewien?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Historia zmian: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Zaznacz %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Zaznacz %s aby zmienić" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Liczba całkowita" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Wartość logiczna (True, False - prawda lub fałsz)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "Łańcuch (do %(maxlength)s znaków)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Liczby całkowite rozdzielone przecinkami" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Data (bez godziny)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Data (z godziną)" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Adres e-mail" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Ścieżka do pliku" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Numer dziesiętny" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Wartość logiczna (True, False, None - prawda, fałsz lub nic)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Relacja do modelu rodzica" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Numer telefonu" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Tekst" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Czas" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Stan USA (dwie duże litery)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "Tekst XML" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Dokumentacja" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Zmiana hasła" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Początek" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Historia" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/czas" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Użytkownik" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Akcja" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Ten obiekt nie ma historii zmian. Najprawdopodobniej wpis te nie " +"został dodany poprzez panel admina" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Administracja stroną Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administracja Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Błąd serwera" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Bład serwera (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Błąd Serwera (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Wystąpił niespodziewany błąd. Raport został wysłany emailem " +"administratorowi strony." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Strona nie znaleziona" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Niestety nie można znaleźć rządanej strony." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modele dostępne w aplikacji %(name)s." + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Dodaj" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Zmień" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Nie masz uprawnień by edytować cokolwiek" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Ostatnie akcje" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Moje akcje" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Brak" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Dodaj %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "" +"Czy zapomniałeś/łaś hasła?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Witaj," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Skasuj" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"Skasowanie %(object_name)s '%(object)s' spowoduje kasację zależnych " +"obiektów, lecz twoje uprawnienia nie pozwalają na usunięcie następujących " +"typów obiektów:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Czy chcesz skasować %(object_name)s \"%(object)s\"? Wszystkie " +"zależne obiekty zostaną skasowane:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Tak, usuń" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "Używając %(title)s" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Szukaj" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Pokaż na stronie" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Proszę popraw poniższy błąd" +msgstr[1] "Proszę popraw poniższe błędy" + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Sortowanie" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Porządek:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Zapisz jako nowe" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Zapisz i dodaj nowe" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Zapisz i kontynuuj edycję" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Zapisz" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Zmiana hasła" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Hasło zmienione" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Twoje hasło zostało zmienione" + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Zresetuj hasło" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Podaj swój adres email. Hasło zostanie zresetowane i wysłane na twój " +"adres email." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Adres e-mail:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Zresetuj moje hasło" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Dziękujemy za odwiedzenie serwisu." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Zaloguj ponownie" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Udane resetowanie hasła" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Nowe hasło zostało wysłane na podany adres email. Powinieneś " +"otrzymać je niebawem." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "Podaj swoje stare hasło i dwa razy nowe." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Stare hasło:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nowe hasło:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Potwierdź hasło:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Zmień hasło" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Otrzymałeś email gdyż zarządałeś zresetowania hasła" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "dla twojego konta użytkownika na stronie %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Twoje nowe hasło to: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Możesz zmienić je na stronie:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Twój login:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Dziękujemy za używanie strony!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Zespół %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Zakładki" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Zakładki Dokumentacji" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                          To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                          \n" +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Dokumentacja dla tej strony" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Pokaż ID obiektu" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Pokazuje typ i unikalne ID dla stron, które reprezentują " +"pojedynczy obiekt." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Edytuj ten obiekt (bierzące okno)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Przeskok do panelu admina dla stron reprezentujących pojedynczy obiekt" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Edytuj ten obiekt (nowe onko)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Jak wyżej, tyle że otwiera nowe okno." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Czas:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Teraz:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Zmień:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "przekieruj z" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Podaj pełną ścieżkę bez nazwy domeny. Przykład: '/" +"events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "przekierowanie do" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "Ścieżka jak wyżej lub pełny URL z http://" + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "przekieruj" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "przekierowania" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Przykład: '/about/contact/'. Upewnij się że wpisałeś otwierający i zamykający slash." + + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "tytuł" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "zawartość" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "włącz komentarze" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nazwa szablonu" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Przykład: 'flatpages/contact_page'. Jeżeli nie podane system użyje " +"'flatpages/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "wymagana rejestracja" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Jeżeli zaznaczone - tylko zalogowani użytkownicy będą mogli zobaczyć stronę." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "strona statyczna" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "strony statyczne" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "nazwa" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "nazwa kodowa" + +#: contrib/auth/models.py:17 +msgid "permission" +msgstr "uprawnienie" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +msgid "permissions" +msgstr "uprawnienia" + +#: contrib/auth/models.py:29 +msgid "group" +msgstr "grupa" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +msgid "groups" +msgstr "grupy" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "użytkownik" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "Imię" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "Nazwisko" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "adres e-mail" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "hasło" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Użyj '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "w zespole" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Oznacza czy użytkownik może zalogować się do panelu admina." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "aktywny" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "Główny Administrator" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "ostatnio zalogowany" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "data przyłączenia" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Oprócz uprawnień przypisanych bezpośrednio użytkownikowi otrzyma on " +"uprawnienia grup, do których należy." + +#: contrib/auth/models.py:67 +msgid "user permissions" +msgstr "uprawnienia użytkownika" + +#kurwa +#: contrib/auth/models.py:70 +msgid "user" +msgstr "użytkownik" + +#: contrib/auth/models.py:71 +msgid "users" +msgstr "użytkownicy" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Dane osobowe" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Uprawnienia" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Ważne daty" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grupy" + +#: contrib/auth/models.py:219 +msgid "message" +msgstr "wiadomość" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Twoja przeglądarka nie chce akceptować ciasteczek. Są one " +"wymagane do zalogowania się." + +#: contrib/contenttypes/models.py:25 +msgid "python model class name" +msgstr "nazwa pythonowa modelu klasy" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "typ zawartości" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "typy zawartości" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "klucz sesji" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "data sesji" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "data wygaśnięcia sesji" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "sesja" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sesje" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nazwa domeny" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "wyświetlana nazwa" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "strona" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "strony" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "Y-m-d" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "Y-m-d H:i:s" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "H:i:s" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Poniedziałek" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Wtorek" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Środa" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Czwartek" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Piątek" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sobota" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Niedziela" + +#: utils/dates.py:14 +msgid "January" +msgstr "Styczeń" + +#: utils/dates.py:14 +msgid "February" +msgstr "Luty" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Marzec" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Kwiecień" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maj" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Czerwiec" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Lipiec" + +#: utils/dates.py:15 +msgid "August" +msgstr "Sierpień" + +#: utils/dates.py:15 +msgid "September" +msgstr "Wrzesień" + +#: utils/dates.py:15 +msgid "October" +msgstr "Pażdziernik" + +#: utils/dates.py:15 +msgid "November" +msgstr "Listopad" + +#: utils/dates.py:16 +msgid "December" +msgstr "Grudzień" + +#: utils/dates.py:19 +msgid "jan" +msgstr "sty" + +#: utils/dates.py:19 +msgid "feb" +msgstr "luty" + +#: utils/dates.py:19 +msgid "mar" +msgstr "marz" + +#: utils/dates.py:19 +msgid "apr" +msgstr "kwie" + +#: utils/dates.py:19 +msgid "may" +msgstr "maj" + +#: utils/dates.py:19 +msgid "jun" +msgstr "czerw" + +#: utils/dates.py:20 +msgid "jul" +msgstr "lip" + +#: utils/dates.py:20 +msgid "aug" +msgstr "sier" + +#: utils/dates.py:20 +msgid "sep" +msgstr "wrze" + +#: utils/dates.py:20 +msgid "oct" +msgstr "paź" + +#: utils/dates.py:20 +msgid "nov" +msgstr "list" + +#: utils/dates.py:20 +msgid "dec" +msgstr "gru" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Sty." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Lut." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Sier." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Wrz." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Paź." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Lis." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Gru." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "rok" +msgstr[1] "lat" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "miesiąc" +msgstr[1] "miesięcy" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "tydzień" +msgstr[1] "tygodni" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dzień" +msgstr[1] "dni" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "godzina" +msgstr[1] "godzin" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuta" +msgstr[1] "minut" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengalski" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Czeski" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "Walijski" + +#: conf/global_settings.py:40 +msgid "Danish" +msgstr "Duński" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Niemiecki" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "Grecki" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Angielski" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Hiszpański" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Francuski" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galicyjnski" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "Hebrajski" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Islandzki" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Włoski" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japoński" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Holenderski" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norweski" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brazylijski" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Rumuński" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Rosyjski" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Słowacki" + +#: conf/global_settings.py:58 +msgid "Slovenian" +msgstr "Słowacki" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Serbski" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Szwedzki" + +#: conf/global_settings.py:61 +msgid "Ukrainian" +msgstr "Ukraiński" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Uproszczony Chiński" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Chiński tradycyjny" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "To pole możei zawierać tylko litery, cyfry i podkreślenia" + +#: core/validators.py:64 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "To pole może zawierać jedynie litery, cyfry, podkreślenia i slasze." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Wielkie litery nie są tutaj dozwolone" + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Małe litery nie są tutaj dozwolone" + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Wpisz tylko cyfry odddzielone przecinkami" + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Wpisz poprawne adresy e-mai oddzielone przecinkamil" + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Proszę wpisać poprawny adres IP" + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Proszę wypełnić te pola" + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Tu mogą być tylko cyfry" + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "To pole nie może zawierać jedynie cyfr." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Wpisz całą liczbę" + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Tutaj są dozwolone tylko litery" + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Proszę wpisać poprawną datę w formacie RRRR-MM-DD." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Proszę wpisać poprawny czas w formacie GG:MM" + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Wprowadź poprawną datę i czas w formacie RRRR-MM-DD GG:MM" + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Wprowadź poprawny adres e-mail" + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Wgraj poprawny plik graficzny. Ten, który został wgrany jest niepoprawny " +"albo uszkodzony." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "Odnośnik %s nie wskazuje na poprawny plik z obrazem." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Numery telefoniczne muszą być w formacie XXX-XXX-XXXX. \"%s\" jest " +"niepoprawny." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "Odnośnik %s nie wskazuje na poprawne plik QuickTime video." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Wymagany jest poprawny URL." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"Wymagany jest poprawny odnośnik. Błędy to:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "Nieprawidłowy format XML: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "Niepoprawny odnośnik: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "Odnośnik %s jest nieprawidłowy." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Wpisz poprawny kod stanu U.S.A." + +#: core/validators.py:229 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Nie wolno przeklinać! Słowo %s jest niedozwolone." +msgstr[1] "Nie wolno przeklinać! Słowa %s są niedozwolone." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "To pole musi pasować do pola '%s'." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Proszę wpisać cokolwiek do chociaż jednego pola." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Proszę uzupełnić oba pola lub zostawić je puste." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "To pole musi być uzupełnione jeśli %(field)s jest %(value)s" + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "To pole musi być wypełnione jeżeli %(field)s nie jest %(value)s" + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Duplikaty są niedozwolone." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "" + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Proszę wpisać poprawną liczbę dziesiętną." + +#: core/validators.py:349 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfry." +msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną zawierającą nie więcej niż %s cyfr." + +#: core/validators.py:352 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Proszę wpisać poprawną liczbę dziesiętną z dokładnością do %s miejsca po przecinku." +msgstr[1] "Proszę wpisać poprawną liczbę dziesiętną z dokładnością do %s miejsc po przecinku." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Upewnij się, że wgrany plik ma conajmniej %s bajtów." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Upewnij się, że wgrany plik nie zawiera więcej niż %s bajtów." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "Format tego pola jest nieprawidłowy." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "To pole jest nieprawidłowe." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Nie można nic pobrać z %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"URL %(url)s zwrócił niepoprawny Content-Type header '%(contenttype)s'." + + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "" + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "" + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "To pole jest wymagane" + +#: db/models/fields/__init__.py:337 +msgid "This value must be an integer." +msgstr "Ta wartość musi być liczbą całkowitą" + +#: db/models/fields/__init__.py:369 +msgid "This value must be either True or False." +msgstr "Ta wartość musi być logiczna (True, False - prawda lub fałsz)." + +#: db/models/fields/__init__.py:385 +msgid "This field cannot be null." +msgstr "To pole nie może być puste." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Wpisz poprawną nazwę pliku." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Proszę wpisać poprawne %s." + +#: db/models/fields/related.py:579 +msgid "Separate multiple IDs with commas." +msgstr "Oddziel kilka pól ID przecinkami." + +#: db/models/fields/related.py:581 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +" Trzymaj przyciśnięty klawisz \"Ctrl\", lub \"Command\" na Macu aby " +"zaznaczyć więcej niż jeden wybór." + +#: db/models/fields/related.py:625 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +msgstr[1] "" + +#: forms/__init__.py:380 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Upewnij się, że tekst ma mniej niż %s znak." +msgstr[1] "Upewnij się, że tekst ma mniej niż %s znaków." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Znaki nowego wiersza są tutaj niedopuszczalne." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "" + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "Wgrany plik jest pusty." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Proszę wpisać liczbę z zakresu od -32 768 do 32 767" + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Proszę wpisać liczbę dodatnią." + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Proszę wpisać liczbę z zakresu od 0 do 32 767" + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "tak,nie, może" + +#~ msgid "String (up to 50)" +#~ msgstr "Ciąg znaków (do ilości 50 znaków)" + +#~ msgid "Comment" +#~ msgstr "Komentarz" + +#~ msgid "Comments" +#~ msgstr "Komentarze" + +#~ msgid "label" +#~ msgstr "etykieta" + +#~ msgid "package" +#~ msgstr "pakiet" + +#~ msgid "packages" +#~ msgstr "pakiety" diff --git a/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..752211a45454449d03df0fa1a66c06db3b55f1c0 GIT binary patch literal 1564 zcwSYL!EYQj6o=h(6UY_{lpro3Jt4KUf>~zLw1iEzG~F~+lWe-mZdl;vopE=acZFhRuHtn+ zRD7L3RlM%!iueCsuK$UA+U2SEdS@}{403MT$G?ojZDcU*_xo_#=lQhse^K%)9OjS@ zkPYOI$j6W~Gv0rC#?R-F?n>QRo&Nvk?tPj(+MXi5Hg_Mh<(j(-SBJ}mM=#Fc&T|_3 zv&7f#nmfy>(tgGbmx0zsHL{WIEXQ8DvJw)w0I)ZT_&HimYGJjhq-7F!8>d)~Wq0grsi0gg0q&>`vyGkZ zTCjMmb5-4DI=C)lm2f$z!(yM?>f6B$CYko-Zd{{%CNO^#)fTF4k!dP|HKj+U4j|KF zZWrNA1z?cYQ<}WN& z@p)ruimc*C-Nn(^qmNTrJn)V3f#+mJW+K}qy>Xv2okN^c^Kf!t^0Bdd$}PA|F0wa{ z^YP^V(fh~Vci8}cm5xOZo7{uV%oeXbXEAIXa29j7+M>JA%$arzu*p-04(^f`-X5w= zVYTVx+ayPa z`9$M((8k|NkuA<#w4nF^WMR|EA=^Wp(g@Vz4WayiYK!wWKYGuxqx#BHvYO-b#0y-{ N@QIOLoGXp9{{UtOjIsa# literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..8b929f3 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/pl/LC_MESSAGES/djangojs.po @@ -0,0 +1,112 @@ +# translation of djangojs.po to Polish +# Copyright (C) 2007 Michal Chruszcz +# This file is distributed under the same license as the django package. +# +# Michal Chruszcz , 2007. +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2007-03-12 11:42+0100\n" +"Last-Translator: Michal Chruszcz \n" +"Language-Team: Polish \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Dostępne %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Wybierz wszystko" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Dodaj" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Usuń" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Wybrano %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +#, fuzzy +msgid "Select your choice(s) and click " +msgstr "Zaznacz swój wybór i kliknij " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Wyczyść wszystko" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "Styczeń Luty Marzec Kwiecień Maj Czerwiec Lipiec Sierpień Wrzesień Październik Listopad Grudzień" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Niedziela Poniedziałek Wtorek Środa Czwartek Piątek Sobota" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "N Pn Wt Śr Cz Pt So" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Teraz" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Zegar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Wybierz czas" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Północ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 rano" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Południe" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Anuluj" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Dzisiaj" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Kalendarz" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Wczoraj" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Jutro" + diff --git a/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..7adc41be5b09b085c493cfae6e9851dd140b0600 GIT binary patch literal 37681 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ñc2lv#- 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 grsP6>#4hCC~@, 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 0.96pre\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-03-15 15:43+0200\n" +"PO-Revision-Date: 2007-03-16 10:00+0000\n" +"Last-Translator: Nuno Mariz \n" +"Language-Team: pt_PT \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "ID do objecto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "título" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentário" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "avaliação #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "avaliação #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "avaliação #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "avaliação #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "avaliação #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "avaliação #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "avaliação #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "avaliação #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "é uma avaliação válida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/hora de submissão" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "é público" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:304 +msgid "IP address" +msgstr "Endereço IP" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "foi removido" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "Seleccione esta opção se o comentário não é apropriado. Uma mensagem \"Este comentário foi removido\" será mostrada no seu lugar." + +#: contrib/comments/models.py:91 +msgid "comments" +msgstr "comentários" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Objecto de conteúdo" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Colocado pelo utilizador %(user)s em %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nome da pessoa" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "endereço ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprovado pela equipa" + +#: contrib/comments/models.py:176 +msgid "free comment" +msgstr "comentário livre" + +#: contrib/comments/models.py:177 +msgid "free comments" +msgstr "comentários livres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "pontuação" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data da pontuação" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "pontuação do karma" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "pontuações do karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "Avaliação %(score)d por %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"O utilizador %(user)s colocou uma flag neste comentário\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "data da flag" + +#: contrib/comments/models.py:268 +msgid "user flag" +msgstr "flag do utilizador" + +#: contrib/comments/models.py:269 +msgid "user flags" +msgstr "flags do utilizador" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Flag por %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data de remoção" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "remoção pelo moderador" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "remoções pelo moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Remoção de moderador %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Utilizadores anónimos não podem votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de comentário inválido" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Não pode votar em si" + +#: contrib/comments/views/comments.py:27 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "Esta avaliação é obrigatória porque introduziu pelo menos uma outra avaliação." + +#: contrib/comments/views/comments.py:111 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Este comentário foi colocado por um utilizador que efectuou menos de %(count)s comentário:\n" +"\n" +"%(text)s" +msgstr[1] "" +"Este comentário foi colocado por um utilizador que efectuou menos de %(count)s comentários:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:116 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentário foi colocado por um utilizador incompleto:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:188 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Apenas POSTs são autorizados" + +#: contrib/comments/views/comments.py:192 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Um ou mais campos obrigatórios não foram submetidos" + +#: contrib/comments/views/comments.py:196 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Alguém modificou o formulário de comentário (violação de segurança)" + +#: contrib/comments/views/comments.py:206 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "O formulário de comentário teve um parâmetro 'target' inválido -- o ID do objecto foi inválido" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "O formulário de comentário não forneceu nem 'preview' ou 'post'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Utilizador:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Sair" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Palavra-passe:" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "Esqueceu-se da palavra-passe?" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "Avaliações" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Obrigatório" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Colocar uma foto" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "Comentário:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "Pré-visualizar comentário" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "O seu nome:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                          By %s:

                                                                                          \n" +"
                                                                                            \n" +msgstr "" +"

                                                                                            Por %s:

                                                                                            \n" +"
                                                                                              \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 +msgid "All" +msgstr "Todos" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Qualquer data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Hoje" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últimos 7 dias" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Este mês" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Este ano" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Sim" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Não" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Desconhecido" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "hora da acção" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id do objecto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr do objecto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "flag de acção" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "modificar mensagem" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada de log" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entradas de log" + +#: contrib/admin/templatetags/admin_list.py:230 +msgid "All dates" +msgstr "Todas as datas" + +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:59 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "Por favor introduza o utilizador e palavra-passe correctos. Note que ambos os casos diferenciam maiúsculas e minúsculas." + +#: contrib/admin/views/decorators.py:24 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Entrar" + +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "Por favor autentique-se novamente, porque a sua sessão expirou. Não se preocupe: Os dados submetidos foram gravados." + +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "Aparentemente o seu browser não está configurado para aceitar cookies. Por favor active os cookies, carrege novamente a página e volte a tentar." + +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "Nomes de utilizador não podem conter o caracter '@'." + +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "O seu endereço de e-mail não é o seu nome de utilizador. Tente usar '%s'." + +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "Administração do site" + +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:17 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi adicionado(a) com sucesso." + +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:22 +msgid "You may edit it again below." +msgstr "Pode editá-lo(a) outra vez abaixo." + +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "Pode adicionar outro %s abaixo." + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "Adicionar %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "Foi adicionado %s" + +#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337 +#: contrib/admin/views/main.py:339 +msgid "and" +msgstr "e" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "Foi modificado %s." + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "Foi removido %s." + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "Nenhum campo foi modificado." + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi modificado(a) com sucesso." + +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "O(A) %(name)s \"%(obj)s\" foi adicionado(a) com sucesso. Pode voltar a editar novamente abaixo." + +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:473 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Um ou mais %(fieldname)s em %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:478 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Um ou mais %(fieldname)s em %(name)s:" + +#: contrib/admin/views/main.py:511 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi removido(a) com sucesso." + +#: contrib/admin/views/main.py:514 +msgid "Are you sure?" +msgstr "Tem a certeza?" + +#: contrib/admin/views/main.py:536 +#, python-format +msgid "Change history: %s" +msgstr "Histórico de modificações: %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s" +msgstr "Seleccionar %s" + +#: contrib/admin/views/main.py:570 +#, python-format +msgid "Select %s to change" +msgstr "Seleccione %s para modificar" + +#: contrib/admin/views/main.py:758 +msgid "Database error" +msgstr "Erro de base de dados" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "tag:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "filtro:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "ver:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "A aplicação %r não encontrada" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %r not found in app %r" +msgstr "O Model %r não foi encontrado na aplicação %r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%s.%s` object" +msgstr "o objecto `%s.%s` relacionado" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "model:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%s.%s` objects" +msgstr "os objectos `%s.%s` relacionados" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "todos %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "número de %s" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "Campos nos objectos %s" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "Inteiro" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "Boolean (Pode ser True ou False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "String (até %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "Inteiros separados por virgula" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "Data (sem hora)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "Data (com hora)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" +msgstr "Endereço de e-mail" + +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "Caminho do ficheiro" + +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "Número décimal" + +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "Boolean (Pode ser True, False ou None)" + +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "Relação para o pai do model" + +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "Número de telefone" + +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "Texto" + +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:315 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "Estado dos E.U.A (duas letras em maiúsculas)" + +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "Texto XML" + +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s não parece ser um objecto urlpattern" + +#: contrib/admin/views/auth.py:28 +msgid "Add user" +msgstr "Adicionar utilizador" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Documentação" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Modificar palavra-passe" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Início" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "História" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Utilizador" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Acção" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "Este objecto não tem histórico de modificações. Provavelmente não foi modificado via site de administração." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Site de administração do Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administração do Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Erro do servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Erro do servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Erro do servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "Ocorreu um erro. Foi reportado aos administradores do site via e-mail e deverá ser corrigido brevemente. Obrigado pela sua paciência." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Página não encontrada" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Pedimos desculpa, mas a página solicitada não foi encontrada." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Models disponíveis na aplicação %(name)s." + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Adicionar" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Não tem permissão para modificar nada." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Acções Recentes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "As minhas Acções" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nenhum disponível" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Adicionar %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Esqueceu-se a sua palavra-passe?" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "Bem-vindo," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Remover" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "A remoção de %(object_name)s '%(escaped_objects)s' resultará na remoção dos objectos relacionados, mas a sua conta não tem permissão de remoção dos seguintes tipos de objectos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "Tem a certeza que deseja remover %(object_name)s \"%(escaped_object)s\"? Todos os items relacionados seguintes irão ser removidos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Sim, tenho a certeza" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " Por %(filter_title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Ir" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 resultado" +msgstr[1] "%(counter)s resultados" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "%(full_result_count)s no total" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "Mostrar todos" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "Filtro" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Ver no site" + +#: contrib/admin/templates/admin/change_form.html:30 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Por favor corrija o erro abaixo." +msgstr[1] "Por favor corrija os erros abaixo." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Ordenação" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Ordem:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Gravar como novo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Gravar e adicionar outro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Gravar e continuar a editar" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Gravar" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "Passa-se algo de errado com a instalação da sua base de dados. Verifique se as tabelas da base de dados foram criadas apropriadamente e verifique se a base de dados pode ser lida pelo utilizador definido." + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "Primeiro introduza o nome do utilizador e palavra-passe. Depois poderá editar mais opções do utilizador." + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "Utilizador" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +msgid "Password" +msgstr "Palavra-passe" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +msgid "Password (again)" +msgstr "Palavra-passe (novamente)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +msgid "Enter the same password as above, for verification." +msgstr "Introduza a palavra-passe como acima, para verificação." + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Modificação de palavra-passe" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Palavra-passe modificada com sucesso" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "A sua palavra-passe foi modificada." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Reinicializar palavra-passe" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "Esqueceu-se da palavra-passe? Introduza o seu email abaixo, e enviaremos a sua palavra-passe reinicializada para o seu e-mail." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Endereço de e-mail:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Reinicializar a minha palavra-passe" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Obrigado por ter gasto tempo de qualidade no Web site hoje." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Entrar novamente" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Palavra-passe reinicializada com sucesso" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "Foi enviada uma nova palavra-passe nova para o e-mail que submeteu. Deverá estar a recebê-la brevemente." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "Por razões de segurança, por favor introduza a sua palavra-passe antiga e depois introduza a nova duas vezes para que possamos verificar se introduziu correctamente." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Palavra-passe antiga:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nova password:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirmação da palavra-passe:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Modificar a minha palavra-passe" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Está a receber este e-mail porque requisitou a reinicialização da sua palavra-passe" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "para a sua conta de utilizador em %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "A sua nova palavra-chave é: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Tenha a liberdade de modificar esta palavra-passe através desta página:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "O seu nome de utilizador, no caso de se ter esquecido:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Obrigado pela sua visita ao nosso site!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "A equipa do %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Itens do bookmark" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Documentação dos itens do bookmark" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                              To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                              \n" +msgstr "" +"\n" +"

                                                                                              Para instalar itens no bookmark, arraste o link para sua barra \n" +"de bookmarks, ou clique com o lado direito do rato no link e adicione ao seus bookmarks. Agora pode \n" +"seleccionar o link do bookmark de qualquer página no site. Note que alguns destes \n" +"itens do bookmark requerem que visualize o site de um computador designado \n" +"por \"internal\" (entre em contacto com o seu administrador de sistema se \n" +"não tiver a certeza se o seu computador é \"internal\".

                                                                                              \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentação desta página" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "Vai de qualquer página para a documentação da view que gera essa página." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostrar o ID do objecto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "Mostra o tipo de conteúdo e o ID único para as páginas que representam um único objecto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar este objecto (janela actual)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "Vai para a página de admin para as páginas que representam um único objecto." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar este objecto (nova janela)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Tal como acima, mas abre a página de admin numa nova janela." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Actualmente:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redireccionar de" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "Isto deverá ser um caminho absoluto, excluindo o domínio. Exemplo: '/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redireccionar para" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "Isto poderá ser um caminho absoluto (como acima) ou um URL completo começado por 'http://'." + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "redireccionar" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "redirecciona" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Exemplo: '/about/contact/'. Verifique se possui as barras no inicio e no fim." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "titulo" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "conteúdo" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "permitir comentários" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nome da template" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "Exemplo: 'flatpages/contact_page.html'. Se não for fornecido, o sistema usará: 'flatpages/default.html'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "é necessário registo" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Se estiver seleccionado, apenas utilizadores autenticados poderão ver esta página." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "página plana" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "páginas planas" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "Saiu" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "nome" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "nome de código" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "permissão" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "permissões" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "grupo" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "grupos" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "utilizador" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "Obrigatório. 30 caracteres ou menos. Apenas caracteres alfanúmericos (letras, números ou underscores)." + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "primeiro nome" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "último nome" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "endereço de e-mail" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "palavra-passe" + +#: contrib/auth/models.py:94 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Use '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "status de equipa" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "Define se o utilizador pode usar a administração do site." + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "activo" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "Define se este utiliador pode usar a adminstração do site. Não seleccione em vez de remover as contas." + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "Status de superuser" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "Define se este utilizador tem todas as permissões sem explicitamente as atribuir." + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "última entrada" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "data de registo" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "Em adição às permissões definidas manualmente, este utilizador também terá todas as permissões atribuídas a cada grupo a que partence." + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "permissões do utilizador" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "utilizador" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "utilizadores" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "Informação pessoal" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "Permissões" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "Datas importantes" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "Grupos" + +#: contrib/auth/models.py:256 +msgid "message" +msgstr "mensagem" + +#: contrib/auth/forms.py:52 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "Aparentemente que o seu browser não está configurado para aceitar cookies. Os cookies são necessários para poder entrar." + +#: contrib/auth/forms.py:61 +msgid "This account is inactive." +msgstr "Esta conta não está activa." + +#: contrib/contenttypes/models.py:20 +msgid "python model class name" +msgstr "python model class name" + +#: contrib/contenttypes/models.py:23 +msgid "content type" +msgstr "tipo de conteúdo" + +#: contrib/contenttypes/models.py:24 +msgid "content types" +msgstr "tipos de conteúdos" + +#: contrib/sessions/models.py:51 +msgid "session key" +msgstr "chave da sessão" + +#: contrib/sessions/models.py:52 +msgid "session data" +msgstr "dados da sessão" + +#: contrib/sessions/models.py:53 +msgid "expire date" +msgstr "data de expiração" + +#: contrib/sessions/models.py:57 +msgid "session" +msgstr "sessão" + +#: contrib/sessions/models.py:58 +msgid "sessions" +msgstr "sessões" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nome do domínio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "mostrar nome" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "site" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sites" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Segunda-feira" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Terça-feira" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Quarta-feira" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Quinta-feira" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Sexta-feira" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sábado" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Domingo" + +#: utils/dates.py:14 +msgid "January" +msgstr "Janeiro" + +#: utils/dates.py:14 +msgid "February" +msgstr "Fevereiro" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Março" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maio" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Junho" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Julho" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "Setembro" + +#: utils/dates.py:15 +msgid "October" +msgstr "Outubro" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembro" + +#: utils/dates.py:16 +msgid "December" +msgstr "Dezembro" + +#: utils/dates.py:19 +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "fev" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "out" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dez" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Fev." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Out." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dez." + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "ano" +msgstr[1] "anos" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "mês" +msgstr[1] "meses" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semana" +msgstr[1] "semanas" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "dia" +msgstr[1] "dias" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "horas" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minutos" + +#: utils/translation/trans_real.py:362 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation/trans_real.py:363 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation/trans_real.py:364 +msgid "TIME_FORMAT" +msgstr "P" + +#: utils/translation/trans_real.py:380 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:381 +msgid "MONTH_DAY_FORMAT" +msgstr "F j" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "Árabe" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "Bengalês" + +#: conf/global_settings.py:41 +msgid "Czech" +msgstr "Checo" + +#: conf/global_settings.py:42 +msgid "Welsh" +msgstr "Galês" + +#: conf/global_settings.py:43 +msgid "Danish" +msgstr "Dinamarquês" + +#: conf/global_settings.py:44 +msgid "German" +msgstr "Alemão" + +#: conf/global_settings.py:45 +msgid "Greek" +msgstr "Grego" + +#: conf/global_settings.py:46 +msgid "English" +msgstr "Inglês" + +#: conf/global_settings.py:47 +msgid "Spanish" +msgstr "Espanhol" + +#: conf/global_settings.py:48 +msgid "Argentinean Spanish" +msgstr "Espanhol Argentino" + +#: conf/global_settings.py:49 +msgid "Finnish" +msgstr "Filandês" + +#: conf/global_settings.py:50 +msgid "French" +msgstr "Francês" + +#: conf/global_settings.py:51 +msgid "Galician" +msgstr "Galaciano" + +#: conf/global_settings.py:52 +msgid "Hungarian" +msgstr "Húngaro" + +#: conf/global_settings.py:53 +msgid "Hebrew" +msgstr "Hebraico" + +#: conf/global_settings.py:54 +msgid "Icelandic" +msgstr "Islandês" + +#: conf/global_settings.py:55 +msgid "Italian" +msgstr "Italiano" + +#: conf/global_settings.py:56 +msgid "Japanese" +msgstr "Japonês" + +#: conf/global_settings.py:57 +msgid "Dutch" +msgstr "Holandês" + +#: conf/global_settings.py:58 +msgid "Norwegian" +msgstr "Norueguês" + +#: conf/global_settings.py:59 +msgid "Brazilian" +msgstr "Brasileiro" + +#: conf/global_settings.py:60 +msgid "Romanian" +msgstr "Romeno" + +#: conf/global_settings.py:61 +msgid "Russian" +msgstr "Russo" + +#: conf/global_settings.py:62 +msgid "Slovak" +msgstr "Eslovaco" + +#: conf/global_settings.py:63 +msgid "Slovenian" +msgstr "Esloveno" + +#: conf/global_settings.py:64 +msgid "Serbian" +msgstr "Sérvio" + +#: conf/global_settings.py:65 +msgid "Swedish" +msgstr "Sueco" + +#: conf/global_settings.py:66 +msgid "Tamil" +msgstr "Tamil" + +#: conf/global_settings.py:67 +msgid "Turkish" +msgstr "Turco" + +#: conf/global_settings.py:68 +msgid "Ukrainian" +msgstr "Ucraniano" + +#: conf/global_settings.py:69 +msgid "Simplified Chinese" +msgstr "Chinês Simplificado" + +#: conf/global_settings.py:70 +msgid "Traditional Chinese" +msgstr "Chinês Tradicional" + +#: core/validators.py:63 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Este valor apenas poderá conter letras, números ou underscores." + +#: core/validators.py:67 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Este valor apenas poderá conter letras, números, underscores ou traços." + +#: core/validators.py:71 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "Este valor apenas poderá conter letras, números, undercores ou hífenes." + +#: core/validators.py:75 +msgid "Uppercase letters are not allowed here." +msgstr "Letras em maiúsculas não são permitidas aqui." + +#: core/validators.py:79 +msgid "Lowercase letters are not allowed here." +msgstr "Letras em minúsculas não são permitidas aqui." + +#: core/validators.py:86 +msgid "Enter only digits separated by commas." +msgstr "Introduza apenas números separados por vírgulas." + +#: core/validators.py:98 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Introduza endereços de e-mail válidos separados por vírgulas." + +#: core/validators.py:102 +msgid "Please enter a valid IP address." +msgstr "Por favor introduza um endereço IP válido." + +#: core/validators.py:106 +msgid "Empty values are not allowed here." +msgstr "Valores em branco não são permitidos aqui." + +#: core/validators.py:110 +msgid "Non-numeric characters aren't allowed here." +msgstr "Caracteres não númericos não são permitidos aqui." + +#: core/validators.py:114 +msgid "This value can't be comprised solely of digits." +msgstr "Este valor não pode ser constituido apenas por números." + +#: core/validators.py:119 +msgid "Enter a whole number." +msgstr "Introduza um número inteiro." + +#: core/validators.py:123 +msgid "Only alphabetical characters are allowed here." +msgstr "Apenas letras são válidas aqui." + +#: core/validators.py:138 +msgid "Year must be 1900 or later." +msgstr "O ano deve ser 1900 ou superior." + +#: core/validators.py:142 +#, python-format +msgid "Invalid date: %s." +msgstr "Data inválida: %s." + +#: core/validators.py:146 db/models/fields/__init__.py:415 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Introduza uma data válida no formato AAAA-MM-DD." + +#: core/validators.py:151 +msgid "Enter a valid time in HH:MM format." +msgstr "Introduza uma hora válida no formato HH:MM." + +#: core/validators.py:155 db/models/fields/__init__.py:477 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Introduza uma data/hora válida no formato AAAA-MM-DD HH:MM." + +#: core/validators.py:160 +msgid "Enter a valid e-mail address." +msgstr "Introduza um endereço de e-mail válido." + +#: core/validators.py:172 core/validators.py:401 forms/__init__.py:661 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "Nenhum ficheiro foi submetido. Verifique o tipo de codificação do formulário." + +#: core/validators.py:176 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Introduza uma imagem válida. O ficheiro que introduziu ou não é uma imagem ou está corrompido." + +#: core/validators.py:183 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "O URL %s não aponta para uma imagem válida." + +#: core/validators.py:187 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "Os números de telefone deverão ser no formato XXX-XXX-XXXX. \"%s\" é inválido." + +#: core/validators.py:195 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "O URL %s não aponta para um QuickTime video válido." + +#: core/validators.py:199 +msgid "A valid URL is required." +msgstr "É obrigatório um URL válido" + +#: core/validators.py:213 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"É obrigatório um HTML válido. Os erros específicos são:\n" +"%s" + +#: core/validators.py:220 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formatado: %s" + +#: core/validators.py:230 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL inválido: %s" + +#: core/validators.py:234 core/validators.py:236 +#, python-format +msgid "The URL %s is a broken link." +msgstr "O URL %s é um link quebrado." + +#: core/validators.py:242 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Introduza uma abreviação de um estado dos E.U.A. válido." + +#: core/validators.py:256 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Atenção à linguagem! A palavra %s não é permitida aqui." +msgstr[1] "Atenção à linguagem! As palavras %s não são permitidas aqui." + +#: core/validators.py:263 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Este campo deve ser igual ao campo '%s'." + +#: core/validators.py:282 +msgid "Please enter something for at least one field." +msgstr "Por favor preencha pelo menos um campo." + +#: core/validators.py:291 core/validators.py:302 +msgid "Please enter both fields or leave them both empty." +msgstr "Por favor preencha ambos os campos ou deixe ambos vazios." + +#: core/validators.py:309 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Este campo deve ser preenchido se %(field)s for %(value)s" + +#: core/validators.py:321 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Este campo deve ser preenchido se %(field)s não é %(value)s" + +#: core/validators.py:340 +msgid "Duplicate values are not allowed." +msgstr "Valores duplicados não são permitidos." + +#: core/validators.py:363 +#, python-format +msgid "This value must be a power of %s." +msgstr "Este valor deverá ser uma potência de %s." + +#: core/validators.py:374 +msgid "Please enter a valid decimal number." +msgstr "Por favor introduza um número décimal válido." + +#: core/validators.py:378 +#, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Por favor introduza um número décimal com um máximo de %s digito." +msgstr[1] "Por favor introduza um número décimal com um máximo de %s digitos." + +#: core/validators.py:381 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "Por favor introduza um número décimal com o máximo de % digito na parte inteira." +msgstr[1] "Por favor introduza um número décimal com o máximo de % digitos na parte inteira." + +#: core/validators.py:384 +#, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Por favor introduza um número décimal com o máximo de %s digito na parte décimal." +msgstr[1] "Por favor introduza um número décimal com o máximo de %s digitos na parte décimal." + +#: core/validators.py:394 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Verifique que o ficheiro introduzido tem pelo menos %s bytes." + +#: core/validators.py:395 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Verifique se o ficheiro introduzido tem no máximo %s bytes." + +#: core/validators.py:412 +msgid "The format for this field is wrong." +msgstr "O formato deste campo é errado." + +#: core/validators.py:427 +msgid "This field is invalid." +msgstr "Este campo é inválido." + +#: core/validators.py:463 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Não foi possível extrair nada de %s." + +#: core/validators.py:466 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "O URL %(url)s devolveu um tipo de conteúdo inválido no header: '%s(contenttype)s'." + +#: core/validators.py:499 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "Por favor feche a tag %(tag)s na linha %(line)s. (A linha começa por \"%(start)s\".)" + +#: core/validators.py:503 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "Algum texto começado na linha %(line)s não é permitido nesse contexto. (A linha começa por \"%(start)s\".)" + +#: core/validators.py:508 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "\"%(attr)s\" na linha %(line)s é um atributo inválido. (A linha começa por \"%(start)s\".)" + +#: core/validators.py:513 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "\"<%(tag)s>\" na linha %(line)s é um tag inválida. (A linha começa por \"%(start)s\".)" + +#: core/validators.py:517 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "Uma tag na linha %(line)s não tem um o mais atributos obrigatórios. (A linha começa por \"%(start)s\".)" + +#: core/validators.py:522 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "O atributo \"%(attr)s\" na linha %(line)s tem um valor inválido. (A linha começa por \"%(start)s\".)" + +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "O(A) %(verbose_name)s foi criado(a) com sucesso." + +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "O(A) %(verbose_name)s foi actualizado(a) com sucesso." + +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "O(A) %(verbose_name)s foi removido(a)." + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "O(A) %(object)s com este %(type)s já existe para o(a) %(field)s fornecido." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s com %(fieldname)s já existe." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Este campo é obrigatório." + +#: db/models/fields/__init__.py:340 +msgid "This value must be an integer." +msgstr "Este campo deverá ser inteiro." + +#: db/models/fields/__init__.py:372 +msgid "This value must be either True or False." +msgstr "Este valor deverá ser True ou False." + +#: db/models/fields/__init__.py:388 +msgid "This field cannot be null." +msgstr "Este campo não pode ser nulo." + +#: db/models/fields/__init__.py:571 +msgid "Enter a valid filename." +msgstr "Introduza um nome de ficheiro válido." + +#: db/models/fields/related.py:51 +#, python-format +msgid "Please enter a valid %s." +msgstr "Por favor introduza um %s válido." + +#: db/models/fields/related.py:618 +msgid "Separate multiple IDs with commas." +msgstr "Separe múltiplos IDs através de vírgulas." + +#: db/models/fields/related.py:620 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Mantenha pressionado o \"Control\", or \"Command\" no Mac, para seleccionar mais do que um." + +#: db/models/fields/related.py:664 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "Por favor introduza IDs de %(self)s válidos. O valor %(value)r é inválido." +msgstr[1] "Por favor introduza IDs de %(self)s válidos. Os valores %(value)r são inválidos." + +#: forms/__init__.py:381 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Verifique se o seu texto tem menos de %s caracter." +msgstr[1] "Verifique se o seu texto tem menos de %s caracteres." + +#: forms/__init__.py:386 +msgid "Line breaks are not allowed here." +msgstr "Quebras de linha não são permitas aqui." + +#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Seleccione uma opção válida; '%(data)s' não se encontra em %(choices)s." + +#: forms/__init__.py:663 +msgid "The submitted file is empty." +msgstr "O ficheiro submetido encontra-se vazio." + +#: forms/__init__.py:719 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Introduza um número entre -32,768 e 32,767." + +#: forms/__init__.py:729 +msgid "Enter a positive number." +msgstr "Introduza um número positivo." + +#: forms/__init__.py:739 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Introduza um número entre 0 e 32,767." + +#: template/defaultfilters.py:401 +msgid "yes,no,maybe" +msgstr "sim,não,talvez" diff --git a/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..669dd9d1191866cab33a2ed5aba6e4acd15b36fd GIT binary patch literal 1514 zcwTLjO^6&t6vxZm8Dl1X#PtINg#r-`p~s$;tn0Xnj*_-D)ab-wM_qqeW(9hei~xb5pb2G4@m z!FBNSw(sxfChpfJ?{^q?#qQk`Y&qO_XtV#1M=(z_e?P9LHRjK9b+|ooC3WqtZ`aG? zn2$BNuI5vX|EMz?Ty%>r8>A^4%!JIvI4AyG$+ksKDisEfN+@UB6Rk_)f?HY8e@}V+ zq9j$PPZpNiLIXoJAGdLruCyTv6$GnNsDwcv6|#*)`H361FmMSggG*v@tmS3YC`X7l%e4 zGKh7bcMcEYVB3(e$O(qR zQlGDf;mbkzQqVicd%ga--r2Ahh6oIfXeP^h#BK0i*bfnh(b*HF4PqlynG35;pYK;n zyZe_n_?9Xh-^dDl?rx%s*AcF=N@NtoB#J(t+IJ7*d&P||jCPj2^}1naTPsTnXU(T5 z#?p2B;xrdhy^2d0rX>4XWv>RC%Q#olRWiYjN^~k!*5{jJX*)|D$^@H3cSBiDwOV{V zqkO5y#Z4o1BT2;Ul!?wK0;-&7BbrBu#p+a-&7(}U%ioj56za59sv($s@m+?Epiq)V zL!mP=wTCNOe5v_h>@_O26Y0d+9CncD(rU=msT9!%m8~4TO5qIYM(wZ>Nf9V5Ejd>Q zsT2&^DAKDH{qYD=y@X%%uUuW{_)|HdmQ@Y%4c4v`?t2w+Hq?c5qKYVU8s;&fX2CDv hKV}`I0wR10*u~AUK*#5yOg1Qlnk+tJ2lx~P`v>r5c$okI literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..90f4b9d --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/pt/LC_MESSAGES/djangojs.po @@ -0,0 +1,108 @@ +# Portuguese translation of Django. +# Copyright (C) 2007 the Lawrence Journal-World +# This file is distributed under the same license as the PACKAGE package. +# Nuno Mariz , 2007. +# +msgid "" +msgstr "" +"Project-Id-Version: Django 0.96pre\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2007-03-15 11:51+0100\n" +"PO-Revision-Date: 2007-03-16 10:01+0000\n" +"Last-Translator: Nuno Mariz \n" +"Language-Team: pt_PT \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "Disponível %s" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Escolher todos" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Adicionar" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Remover" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "Escolhido %s" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Seleccione a(s) sua(s) escolha(s) e clique " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Limpar tudo" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "Janeiro Fevereiro Março Abril Maio Junho Julho Agosto Setembro Outubro Novembro Dezembro" + +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Domingo Segunda Terça Quarta Quinta Sexta Sábado" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D S T Q Q S S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Agora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Relógio" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Escolha a hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Meia-noite" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 a.m." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Meio-dia" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Cancelar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Hoje" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendário" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Ontem" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Amanhã" diff --git a/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..7f506f972fe9319df52210ad8c476f8e0e3aabce GIT binary patch literal 28462 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 0000000..bc955f2 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,2051 @@ +# Português do Brasil translation of django. +# Copyright (C) 2006 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# João Marcus Christ , 2006. +# Carlos Eduardo de Paula , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:11+0200\n" +"PO-Revision-Date: 2006-11-01 17:45-0300\n" +"Last-Translator: Carlos Eduardo de Paula \n" +"Language-Team: Português do Brasil \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +msgid "object ID" +msgstr "id do objeto" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "título" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +msgid "comment" +msgstr "comentário" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "avaliação #1" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "avaliação #2" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "avaliação #3" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "avaliação #4" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "avaliação #5" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "avaliação #6" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "avaliação #7" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "avaliação #8" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "é uma avaliação válida" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "data/hora de envio" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "é público" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +msgid "IP address" +msgstr "Endereço IP:" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "foi removido" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Selecione esta opção se o comentário é inapropriado. Uma mensagem \"Este " +"comentário foi removido\" a mensagem será mostrada no lugar." + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "comentários" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +msgid "Content object" +msgstr "Objeto de conteúdo" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"Enviado por %(user)s em %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:168 +msgid "person's name" +msgstr "nome da pessoa" + +#: contrib/comments/models.py:171 +msgid "ip address" +msgstr "endereço ip" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "aprovado pela equipe" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "Comentário livre" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "Comentários livres" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "pontuação" + +#: contrib/comments/models.py:234 +msgid "score date" +msgstr "data de pontuação" + +#: contrib/comments/models.py:237 +#, fuzzy +msgid "karma score" +msgstr "Pontuação de Karma" + +#: contrib/comments/models.py:238 +#, fuzzy +msgid "karma scores" +msgstr "Pontuações de Karma" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "Availação %(score)d por %(user)s" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" +"O usuário %(user)s colocou flags neste documento:\n" +"\n" +"%(text)s" + +#: contrib/comments/models.py:265 +msgid "flag date" +msgstr "flag de data" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "flag de usuário" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "flags de usuário" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "Flag por %r" + +#: contrib/comments/models.py:278 +msgid "deletion date" +msgstr "data de exclusão" + +#: contrib/comments/models.py:280 +#, fuzzy +msgid "moderator deletion" +msgstr "Exclusão feita pelo moderador" + +#: contrib/comments/models.py:281 +#, fuzzy +msgid "moderator deletions" +msgstr "Exclusões feitas pelo moderador" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "Exclusao feita pelo moderador %r" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "Usuários anônimos não podem votar" + +#: contrib/comments/views/karma.py:23 +msgid "Invalid comment ID" +msgstr "ID de comentário inválido" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "Você não pode votar em si mesmo" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" +"Esta avaliação é requerida porque você entrou com ao menos uma avaliação" + +#: contrib/comments/views/comments.py:112 +#, fuzzy, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +"Este comentário foi feito por um usuário que postou menos de %(count)s " +"comentário:\n" +"%(text)s" +msgstr[1] "" +"Este comentário foi feito por um usuário que postou menos de %(count)s " +"comentários:\n" +"%(text)s" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" +"Este comentário foi feito por um usuário incompleto:\n" +"\n" +"%(text)s" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "Somente POSTs são permitidos" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "Um ou mais dos campos requeridos não foram enviados" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "Alguém modificou o form de comentários (violação de segurança)" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"O form de comentários teve um parâmetro 'target' inválido -- o ID do objeto " +"é inválido" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "O form de comentários não forneceu nem 'preview' nem 'post'" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Usuário:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Senha:" + +#: contrib/comments/templates/comments/form.html:6 +#, fuzzy +msgid "Forgotten your password?" +msgstr "Esqueceu sua senha?" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Encerrar sessão" + +#: contrib/comments/templates/comments/form.html:12 +#, fuzzy +msgid "Ratings" +msgstr "Avaliações" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "Requerido" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "Opcional" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "Postar uma foto" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +#, fuzzy +msgid "Comment:" +msgstr "Comentário" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +#, fuzzy +msgid "Preview comment" +msgstr "Pré visualizar comentário" + +#: contrib/comments/templates/comments/freeform.html:4 +#, fuzzy +msgid "Your name:" +msgstr "Seu nome:" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                              By %s:

                                                                                              \n" +"
                                                                                                \n" +msgstr "" +"

                                                                                                Por %s

                                                                                                \n" +"
                                                                                                  \n" + +#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 +#: contrib/admin/filterspecs.py:143 +msgid "All" +msgstr "Todos" + +#: contrib/admin/filterspecs.py:109 +msgid "Any date" +msgstr "Qualquer data" + +#: contrib/admin/filterspecs.py:110 +msgid "Today" +msgstr "Hoje" + +#: contrib/admin/filterspecs.py:113 +msgid "Past 7 days" +msgstr "Últimos 7 dias" + +#: contrib/admin/filterspecs.py:115 +msgid "This month" +msgstr "Este mês" + +#: contrib/admin/filterspecs.py:117 +msgid "This year" +msgstr "Este ano" + +#: contrib/admin/filterspecs.py:143 +msgid "Yes" +msgstr "Sim" + +#: contrib/admin/filterspecs.py:143 +msgid "No" +msgstr "Não" + +#: contrib/admin/filterspecs.py:150 +msgid "Unknown" +msgstr "Desconhecido" + +#: contrib/admin/models.py:16 +msgid "action time" +msgstr "hora da ação" + +#: contrib/admin/models.py:19 +msgid "object id" +msgstr "id do objeto" + +#: contrib/admin/models.py:20 +msgid "object repr" +msgstr "repr do objeto" + +#: contrib/admin/models.py:21 +msgid "action flag" +msgstr "flag de ação" + +#: contrib/admin/models.py:22 +msgid "change message" +msgstr "alterar mensagem" + +#: contrib/admin/models.py:25 +msgid "log entry" +msgstr "entrada de log" + +#: contrib/admin/models.py:26 +msgid "log entries" +msgstr "entradas de log" + +#: contrib/admin/templatetags/admin_list.py:228 +msgid "All dates" +msgstr "Todas as datas" + +#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 +#: contrib/auth/forms.py:41 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Por favor entre usuário e senha corretos. Note que ambos os " +"campos diferenciam maiúsculas e minúsculas." + +#: contrib/admin/views/decorators.py:23 +#: contrib/admin/templates/admin/login.html:25 +msgid "Log in" +msgstr "Acessar" + +#: contrib/admin/views/decorators.py:61 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Por favor acesse novamente, pois sua sessão expirou. Não se preocupe: Os " +"dados enviados foram salvos." + +#: contrib/admin/views/decorators.py:68 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Parece que seu navegador não está configurado para aceitar cookies. Por " +"favor habilite os cookies, recarregue esta página, e tente novamente." + +#: contrib/admin/views/decorators.py:82 +msgid "Usernames cannot contain the '@' character." +msgstr "Nomes de usuário não podem conter o caractere '@'." + +#: contrib/admin/views/decorators.py:84 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "Seu endereço de e-mail não é seu nome de usuário. Tente usar '%s'" + +#: contrib/admin/views/main.py:226 +msgid "Site administration" +msgstr "Administração do Site" + +#: contrib/admin/views/main.py:260 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi adicionado com sucesso." + +#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348 +msgid "You may edit it again below." +msgstr "Você pode editá-lo(a) de novo abaixo." + +#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 +#, python-format +msgid "You may add another %s below." +msgstr "Você pode adicionar outro(a) %s abaixo." + +#: contrib/admin/views/main.py:290 +#, python-format +msgid "Add %s" +msgstr "Adicionar %s" + +#: contrib/admin/views/main.py:336 +#, python-format +msgid "Added %s." +msgstr "Adicionado %s." + +#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 +#: contrib/admin/views/main.py:340 +msgid "and" +msgstr "e" + +#: contrib/admin/views/main.py:338 +#, python-format +msgid "Changed %s." +msgstr "Modificado %s." + +#: contrib/admin/views/main.py:340 +#, python-format +msgid "Deleted %s." +msgstr "Apagado %s." + +#: contrib/admin/views/main.py:343 +msgid "No fields changed." +msgstr "Nenhum campo modificado." + +#: contrib/admin/views/main.py:346 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi modificado com sucesso." + +#: contrib/admin/views/main.py:354 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"O(A) %(name)s \"%(obj)s\" foi adicionado com sucesso. Você pode editá-lo(a) " +"abaixo." + +#: contrib/admin/views/main.py:392 +#, python-format +msgid "Change %s" +msgstr "Modificar %s" + +#: contrib/admin/views/main.py:470 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "Um(a) ou mais %(fieldname)s em %(name)s: %(obj)s" + +#: contrib/admin/views/main.py:475 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "Um(a) ou mais %(fieldname)s em %(name)s:" + +#: contrib/admin/views/main.py:508 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "O(A) %(name)s \"%(obj)s\" foi excluído com sucesso." + +#: contrib/admin/views/main.py:511 +msgid "Are you sure?" +msgstr "Você tem certeza?" + +#: contrib/admin/views/main.py:533 +#, python-format +msgid "Change history: %s" +msgstr "Histórico de Modificações: %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s" +msgstr "Selecione %s" + +#: contrib/admin/views/main.py:565 +#, python-format +msgid "Select %s to change" +msgstr "Selecione %s para modificar" + +#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286 +#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294 +#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 +msgid "Integer" +msgstr "Inteiro" + +#: contrib/admin/views/doc.py:278 +msgid "Boolean (Either True or False)" +msgstr "Lógico (Verdadeiro ou Falso)" + +#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "String (até %(maxlength)s)" + +#: contrib/admin/views/doc.py:280 +msgid "Comma-separated integers" +msgstr "Inteiros separados por vírgula" + +#: contrib/admin/views/doc.py:281 +msgid "Date (without time)" +msgstr "Data (sem hora)" + +#: contrib/admin/views/doc.py:282 +msgid "Date (with time)" +msgstr "Data/hora" + +#: contrib/admin/views/doc.py:283 +msgid "E-mail address" +msgstr "Endereço de e-mail" + +#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 +msgid "File path" +msgstr "Caminho do Arquivo" + +#: contrib/admin/views/doc.py:285 +msgid "Decimal number" +msgstr "Número decimal" + +#: contrib/admin/views/doc.py:291 +msgid "Boolean (Either True, False or None)" +msgstr "Lógico (Verdadeiro, Falso ou Nada)" + +#: contrib/admin/views/doc.py:292 +msgid "Relation to parent model" +msgstr "Relação com o modelo pai" + +#: contrib/admin/views/doc.py:293 +msgid "Phone number" +msgstr "Número de telefone" + +#: contrib/admin/views/doc.py:298 +msgid "Text" +msgstr "Texto" + +#: contrib/admin/views/doc.py:299 +msgid "Time" +msgstr "Hora" + +#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 +msgid "URL" +msgstr "URL" + +#: contrib/admin/views/doc.py:301 +msgid "U.S. state (two uppercase letters)" +msgstr "Estado dos EUA (duas letras maiúsculas)" + +#: contrib/admin/views/doc.py:302 +msgid "XML text" +msgstr "Texto XML" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "Documentação" + +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Change password" +msgstr "Alterar senha" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/change_list.html:6 +#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_form.html:13 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "Início" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:20 +msgid "History" +msgstr "Histórico" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "Data/hora" + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "Usuário" + +#: contrib/admin/templates/admin/object_history.html:20 +msgid "Action" +msgstr "Ação" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "j. N Y, H:i" + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Este objeto não tem um histórico de alterações. Ele provavelmente não foi " +"adicionado por este site de administração." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Site de administração do Django" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Administração do Django" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "Erro no servidor" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "Erro no servidor (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "Erro no Servidor (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Houve um erro. Este foi reportado aos administradores do site através d e-" +"mail e deve ser corrigido em breve. Obrigado pela compreensão." + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "Página não encontrada" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "Desculpe, mas a página requisitada não pode ser encontrada." + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "Modelos disponíveis na aplicação %(name)s" + +#: contrib/admin/templates/admin/index.html:28 +#: contrib/admin/templates/admin/change_form.html:15 +msgid "Add" +msgstr "Adicionar" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "Modificar" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "Você não tem permissão para edição." + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "Ações Recentes" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "Minhas Ações" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "Nenhuma disponível" + +#: contrib/admin/templates/admin/change_list.html:11 +#, python-format +msgid "Add %(name)s" +msgstr "Adicionar %(name)s" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Have you forgotten your password?" +msgstr "Você esqueceu sua senha?" + +#: contrib/admin/templates/admin/base.html:23 +msgid "Welcome," +msgstr "Bem vindo," + +#: contrib/admin/templates/admin/delete_confirmation.html:9 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Delete" +msgstr "Apagar" + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(object)s' would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" +"A remoção de '%(object)s' %(object_name)s pode resultar na remoção de " +"objetos relacionados, mas sua conta não tem a permissão para remoção dos " +"seguintes tipos de objetos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " +"the following related items will be deleted:" +msgstr "" +"Você tem certeza que quer remover o \"%(object)s\" %(object_name)s? Todos os " +"seguintes itens relacionados serão removidos:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "Sim, tenho certeza" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(title)s " +msgstr "Por %(title)s " + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "Ir" + +#: contrib/admin/templates/admin/change_form.html:21 +msgid "View on site" +msgstr "Ver no site" + +#: contrib/admin/templates/admin/change_form.html:30 +#, fuzzy +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "Por favor, corrija o erro abaixo." +msgstr[1] "Por favor, corrija os erros abaixo." + +#: contrib/admin/templates/admin/change_form.html:48 +msgid "Ordering" +msgstr "Ordenação" + +#: contrib/admin/templates/admin/change_form.html:51 +msgid "Order:" +msgstr "Ordem:" + +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "Salvar como novo" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "Salvar e adicionar outro" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "Salvar e continuar editando" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "Salvar" + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +msgid "Password change" +msgstr "Alterar senha" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "Senha alterada com sucesso" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "Sua senha foi alterada." + +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +#: contrib/admin/templates/registration/password_reset_done.html:4 +msgid "Password reset" +msgstr "Reinicializar senha" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Esqueceu a senha? Digite seu e-mail abaixo e nós iremos enviar uma nova " +"senha para você." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "Endereço de e-mail:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "Reinicializar minha senha" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "Obrigado por visitar nosso Web site hoje." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "Acessar novamente" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "Senha inicializada com sucesso" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Nós enviamos uma nova senha para o e-mail que você informou. Você deverá " +"receber uma mensagem em breve." + +#: contrib/admin/templates/registration/password_change_form.html:12 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Por favor, informe sua senha antiga, por segurança, e então informe sua nova " +"senha duas vezes para que possamos verificar que se ela está correta." + +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "Senha antiga:" + +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "Nova senha:" + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "Confirme a senha:" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "Alterar minha senha" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "Você está recebendo este e-mail porque você pediu uma nova senha" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "para sua conta em %(site_name)s" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "Sua nova senha é: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "Sinta-se a vontade para alterar esta senha visitando esta página:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "Seu nome de usuário, caso tenha esquecido:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "Obrigado por usar nosso site!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "Time do %(site_name)s" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "Itens de bookmark" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:5 +msgid "Documentation bookmarklets" +msgstr "Documentação de itens de bookmark" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:9 +msgid "" +"\n" +"

                                                                                                  To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

                                                                                                  \n" +msgstr "" +"\n" +"

                                                                                                  Para instalar um item no bookmark, arraste o link para a \n" +"barra de ferramentas de bookmarks, ou clique com o botão direito no link e\n" +"adicione-o à barra de ferramentas. Agora você pode selecionar o item de\n" +"bookmark de qualquer página do site. Lembre-se que alguns desses itens\n" +"de bookmark requerem que você veja o site de um computador designado\n" +"como \"interno\" (converse com seu administrador de sistemas se você não\n" +"souber se seu computador é \"interno\").

                                                                                                  \n" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:19 +msgid "Documentation for this page" +msgstr "Documentação para esta página" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:20 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"Leva você de qualquer página da documentação para a view que gera tal página." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:22 +msgid "Show object ID" +msgstr "Mostar ID de objeto" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:23 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Mostra o tipo de conteúdo e ID único para páginas que representam um objeto " +"único." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:25 +msgid "Edit this object (current window)" +msgstr "Editar este objeto (janela atual)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:26 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "" +"Vai para a página de administração que representam um objeto " +"único." + +#: contrib/admin/templates/admin_doc/bookmarklets.html:28 +msgid "Edit this object (new window)" +msgstr "Editar este objeto (nova janela)" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:29 +msgid "As above, but opens the admin page in a new window." +msgstr "Como acima, mas abre a página de administração em uma nova janela." + +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "Data:" + +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "Hora:" + +#: contrib/admin/templates/widget/file.html:2 +msgid "Currently:" +msgstr "Atualmente:" + +#: contrib/admin/templates/widget/file.html:3 +msgid "Change:" +msgstr "Modificar:" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "redirecionar de" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Deve conter um caminho absoluto, excluindo o nome de domínio. Exemplo: '/" +"eventos/busca/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "redirecionar para" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Deve conter um caminho absoluto (como acima) ou uma URL completa, começando " +"com 'http://'." + +#: contrib/redirects/models.py:12 +msgid "redirect" +msgstr "redirecionar" + +#: contrib/redirects/models.py:13 +msgid "redirects" +msgstr "redirecionamentos" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Exemplo: '/sobre/contato/'. Lembre-se das barras no começo e no final." + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "título" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "conteúdo" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "habilitar comentários" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "nome do modelo" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page'. If this isn't provided, the system will " +"use 'flatpages/default'." +msgstr "" +"Exemplo: 'flatfiles/contact_page'. Se não for informado, será utilizado " +"'flatfiles/default'." + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "registro obrigatório" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "Se estiver marcado, apenas usuários conectados poderão ver a página." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "página plana" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "páginas planas" + +#: contrib/auth/models.py:13 contrib/auth/models.py:26 +msgid "name" +msgstr "nome" + +#: contrib/auth/models.py:15 +msgid "codename" +msgstr "nome código" + +#: contrib/auth/models.py:17 +#, fuzzy +msgid "permission" +msgstr "permissão" + +#: contrib/auth/models.py:18 contrib/auth/models.py:27 +#, fuzzy +msgid "permissions" +msgstr "permissões" + +#: contrib/auth/models.py:29 +#, fuzzy +msgid "group" +msgstr "grupo" + +#: contrib/auth/models.py:30 contrib/auth/models.py:65 +#, fuzzy +msgid "groups" +msgstr "grupos" + +#: contrib/auth/models.py:55 +msgid "username" +msgstr "usuário" + +#: contrib/auth/models.py:56 +msgid "first name" +msgstr "primeiro nome" + +#: contrib/auth/models.py:57 +msgid "last name" +msgstr "último nome" + +#: contrib/auth/models.py:58 +msgid "e-mail address" +msgstr "endereço de e-mail" + +#: contrib/auth/models.py:59 +msgid "password" +msgstr "senha" + +#: contrib/auth/models.py:59 +msgid "Use '[algo]$[salt]$[hexdigest]'" +msgstr "Use '[algo]$[salt]$[hexdigest]'" + +#: contrib/auth/models.py:60 +msgid "staff status" +msgstr "status da equipe" + +#: contrib/auth/models.py:60 +msgid "Designates whether the user can log into this admin site." +msgstr "Informa se o usuário pode acessar este site de administração." + +#: contrib/auth/models.py:61 +msgid "active" +msgstr "ativar" + +#: contrib/auth/models.py:62 +msgid "superuser status" +msgstr "status de superusuário" + +#: contrib/auth/models.py:63 +msgid "last login" +msgstr "último login" + +#: contrib/auth/models.py:64 +msgid "date joined" +msgstr "data de registro" + +#: contrib/auth/models.py:66 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Em adição às permissões atribuídas manualmente, este usuário também terá " +"todas as permissões dadas a cada grupo que participar." + +#: contrib/auth/models.py:67 +#, fuzzy +msgid "user permissions" +msgstr "permissões do usuário" + +#: contrib/auth/models.py:70 +#, fuzzy +msgid "user" +msgstr "usuário" + +#: contrib/auth/models.py:71 +#, fuzzy +msgid "users" +msgstr "usuários" + +#: contrib/auth/models.py:76 +msgid "Personal info" +msgstr "Informações pessoais" + +#: contrib/auth/models.py:77 +msgid "Permissions" +msgstr "Permissões" + +#: contrib/auth/models.py:78 +msgid "Important dates" +msgstr "Datas importantes" + +#: contrib/auth/models.py:79 +msgid "Groups" +msgstr "Grupos" + +#: contrib/auth/models.py:219 +#, fuzzy +msgid "message" +msgstr "mensagem" + +#: contrib/auth/forms.py:30 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"Seu navegador Web não parece estar com os cookies habilitados. Cookies são " +"requeridos para acessar." + +#: contrib/contenttypes/models.py:25 +#, fuzzy +msgid "python model class name" +msgstr "nome do módulo python" + +#: contrib/contenttypes/models.py:28 +msgid "content type" +msgstr "tipo de conteúdo" + +#: contrib/contenttypes/models.py:29 +msgid "content types" +msgstr "tipos de conteúdo" + +#: contrib/sessions/models.py:35 +msgid "session key" +msgstr "chave da sessão" + +#: contrib/sessions/models.py:36 +msgid "session data" +msgstr "dados da sessão" + +#: contrib/sessions/models.py:37 +msgid "expire date" +msgstr "data de expiração" + +#: contrib/sessions/models.py:41 +msgid "session" +msgstr "sessão" + +#: contrib/sessions/models.py:42 +msgid "sessions" +msgstr "sessões" + +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "nome do domínio" + +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "nome para exibição" + +#: contrib/sites/models.py:15 +msgid "site" +msgstr "site" + +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "sites" + +#: utils/translation.py:360 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation.py:361 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation.py:362 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "Segunda Feira" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "Terça Feira" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "Quarta Feira" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "Quinta Feira" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "Sexta Feira" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "Sábado" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "Domingo" + +#: utils/dates.py:14 +msgid "January" +msgstr "Janeiro" + +#: utils/dates.py:14 +msgid "February" +msgstr "Fevereiro" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "Março" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "Abril" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "Maio" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "Junho" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "Julho" + +#: utils/dates.py:15 +msgid "August" +msgstr "Agosto" + +#: utils/dates.py:15 +msgid "September" +msgstr "Setembro" + +#: utils/dates.py:15 +msgid "October" +msgstr "Outubro" + +#: utils/dates.py:15 +msgid "November" +msgstr "Novembro" + +#: utils/dates.py:16 +msgid "December" +msgstr "Dezembro" + +#: utils/dates.py:19 +#, fuzzy +msgid "jan" +msgstr "jan" + +#: utils/dates.py:19 +msgid "feb" +msgstr "fev" + +#: utils/dates.py:19 +msgid "mar" +msgstr "mar" + +#: utils/dates.py:19 +msgid "apr" +msgstr "abr" + +#: utils/dates.py:19 +#, fuzzy +msgid "may" +msgstr "mai" + +#: utils/dates.py:19 +msgid "jun" +msgstr "jun" + +#: utils/dates.py:20 +msgid "jul" +msgstr "jul" + +#: utils/dates.py:20 +msgid "aug" +msgstr "ago" + +#: utils/dates.py:20 +msgid "sep" +msgstr "set" + +#: utils/dates.py:20 +msgid "oct" +msgstr "out" + +#: utils/dates.py:20 +msgid "nov" +msgstr "nov" + +#: utils/dates.py:20 +msgid "dec" +msgstr "dez" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "Jan." + +#: utils/dates.py:27 +msgid "Feb." +msgstr "Fev." + +#: utils/dates.py:28 +msgid "Aug." +msgstr "Ago." + +#: utils/dates.py:28 +msgid "Sept." +msgstr "Set." + +#: utils/dates.py:28 +msgid "Oct." +msgstr "Out." + +#: utils/dates.py:28 +msgid "Nov." +msgstr "Nov." + +#: utils/dates.py:28 +msgid "Dec." +msgstr "Dez." + +#: utils/timesince.py:12 +#, fuzzy +msgid "year" +msgid_plural "years" +msgstr[0] "ano" +msgstr[1] "anos" + +#: utils/timesince.py:13 +#, fuzzy +msgid "month" +msgid_plural "months" +msgstr[0] "mês" +msgstr[1] "meses" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "semana" +msgstr[1] "semanas" + +#: utils/timesince.py:15 +#, fuzzy +msgid "day" +msgid_plural "days" +msgstr[0] "dia" +msgstr[1] "dias" + +#: utils/timesince.py:16 +#, fuzzy +msgid "hour" +msgid_plural "hours" +msgstr[0] "hora" +msgstr[1] "horas" + +#: utils/timesince.py:17 +#, fuzzy +msgid "minute" +msgid_plural "minutes" +msgstr[0] "minuto" +msgstr[1] "minutos" + +#: conf/global_settings.py:37 +msgid "Bengali" +msgstr "Bengalês" + +#: conf/global_settings.py:38 +msgid "Czech" +msgstr "Tcheco" + +#: conf/global_settings.py:39 +msgid "Welsh" +msgstr "" + +#: conf/global_settings.py:40 +#, fuzzy +msgid "Danish" +msgstr "Dinamarquês" + +#: conf/global_settings.py:41 +msgid "German" +msgstr "Alemão" + +#: conf/global_settings.py:42 +msgid "Greek" +msgstr "Grego" + +#: conf/global_settings.py:43 +msgid "English" +msgstr "Inglês" + +#: conf/global_settings.py:44 +msgid "Spanish" +msgstr "Espanhol" + +#: conf/global_settings.py:45 +msgid "French" +msgstr "Francês" + +#: conf/global_settings.py:46 +msgid "Galician" +msgstr "Galiciano" + +#: conf/global_settings.py:47 +msgid "Hungarian" +msgstr "Húngaro" + +#: conf/global_settings.py:48 +msgid "Hebrew" +msgstr "Hebraico" + +#: conf/global_settings.py:49 +msgid "Icelandic" +msgstr "Islandês" + +#: conf/global_settings.py:50 +msgid "Italian" +msgstr "Italiano" + +#: conf/global_settings.py:51 +msgid "Japanese" +msgstr "Japonês" + +#: conf/global_settings.py:52 +msgid "Dutch" +msgstr "Alemão" + +#: conf/global_settings.py:53 +msgid "Norwegian" +msgstr "Norueguês" + +#: conf/global_settings.py:54 +msgid "Brazilian" +msgstr "Brasileiro" + +#: conf/global_settings.py:55 +msgid "Romanian" +msgstr "Romeno" + +#: conf/global_settings.py:56 +msgid "Russian" +msgstr "Russo" + +#: conf/global_settings.py:57 +msgid "Slovak" +msgstr "Eslovaco" + +#: conf/global_settings.py:58 +#, fuzzy +msgid "Slovenian" +msgstr "Esloveno" + +#: conf/global_settings.py:59 +msgid "Serbian" +msgstr "Sérvio" + +#: conf/global_settings.py:60 +msgid "Swedish" +msgstr "Sueco" + +#: conf/global_settings.py:61 +#, fuzzy +msgid "Ukrainian" +msgstr "Ucraniano" + +#: conf/global_settings.py:62 +msgid "Simplified Chinese" +msgstr "Chinês Simplificado" + +#: conf/global_settings.py:63 +msgid "Traditional Chinese" +msgstr "Chinês Tradicional" + +#: core/validators.py:60 +msgid "This value must contain only letters, numbers and underscores." +msgstr "Deve conter apenas letras, números e sublinhados (_)." + +#: core/validators.py:64 +#, fuzzy +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "Deve conter apenas letras, números, sublinhados (_) e barras (/)." + +#: core/validators.py:72 +msgid "Uppercase letters are not allowed here." +msgstr "Letras em maiúsculo não são permitidas aqui." + +#: core/validators.py:76 +msgid "Lowercase letters are not allowed here." +msgstr "Letras em minúsculo não são permitidas aqui." + +#: core/validators.py:83 +msgid "Enter only digits separated by commas." +msgstr "Informe apenas dígitos separados por vírgulas." + +#: core/validators.py:95 +msgid "Enter valid e-mail addresses separated by commas." +msgstr "Informe endereços de email válidos separados por vírgulas." + +#: core/validators.py:99 +msgid "Please enter a valid IP address." +msgstr "Informe um endereço IP válido." + +#: core/validators.py:103 +msgid "Empty values are not allowed here." +msgstr "Valores em branco não são permitidos." + +#: core/validators.py:107 +msgid "Non-numeric characters aren't allowed here." +msgstr "Caracteres não numéricos não são permitidos." + +#: core/validators.py:111 +msgid "This value can't be comprised solely of digits." +msgstr "Este valor não pode conter apenas dígitos." + +#: core/validators.py:116 +msgid "Enter a whole number." +msgstr "Informe um número completo." + +#: core/validators.py:120 +msgid "Only alphabetical characters are allowed here." +msgstr "Apenas caracteres do alfabeto são permitidos aqui." + +#: core/validators.py:124 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "Informe uma data válida no formato AAAA-MM-DD." + +#: core/validators.py:128 +msgid "Enter a valid time in HH:MM format." +msgstr "Informe uma hora válida no formato HH:MM." + +#: core/validators.py:132 db/models/fields/__init__.py:468 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "Informe uma data/hora válida no formato AAAA-MM-DD HH:MM." + +#: core/validators.py:136 +msgid "Enter a valid e-mail address." +msgstr "Informe um endereço de email válido." + +#: core/validators.py:148 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envie uma imagem válida. O arquivo enviado não é uma imagem ou está " +"corrompido." + +#: core/validators.py:155 +#, python-format +msgid "The URL %s does not point to a valid image." +msgstr "A URL %s não aponta para um imagem válida." + +#: core/validators.py:159 +#, python-format +msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." +msgstr "" +"Números de telefone deves estar no formato XXX-XXX-XXXX.\"%s\" é inválido." + +#: core/validators.py:167 +#, python-format +msgid "The URL %s does not point to a valid QuickTime video." +msgstr "A URL %s não aponta para um vídeo QuickTime válido." + +#: core/validators.py:171 +msgid "A valid URL is required." +msgstr "Uma URL válida é exigida." + +#: core/validators.py:185 +#, python-format +msgid "" +"Valid HTML is required. Specific errors are:\n" +"%s" +msgstr "" +"HTML válido é exigido. Estes são os erros específicos:\n" +"%s" + +#: core/validators.py:192 +#, python-format +msgid "Badly formed XML: %s" +msgstr "XML mal formado: %s" + +#: core/validators.py:202 +#, python-format +msgid "Invalid URL: %s" +msgstr "URL inválida: %s" + +#: core/validators.py:206 core/validators.py:208 +#, python-format +msgid "The URL %s is a broken link." +msgstr "A URL %s é um link quebrado." + +#: core/validators.py:214 +msgid "Enter a valid U.S. state abbreviation." +msgstr "Informe uma abreviação válida de nome de um estado dos EUA." + +#: core/validators.py:229 +#, fuzzy, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "Lave sua boca! A palavra %s não é permitida aqui." +msgstr[1] "Lave sua boca! As palavras %s não são permitidas aqui." + +#: core/validators.py:236 +#, python-format +msgid "This field must match the '%s' field." +msgstr "Este campo deve ser igual ao campo '%s'." + +#: core/validators.py:255 +msgid "Please enter something for at least one field." +msgstr "Informe algo em pelo menos um campo." + +#: core/validators.py:264 core/validators.py:275 +msgid "Please enter both fields or leave them both empty." +msgstr "Informe ambos os campos ou deixe ambos vazios." + +#: core/validators.py:282 +#, python-format +msgid "This field must be given if %(field)s is %(value)s" +msgstr "Este campo deve ser informado se o campo %(field)s for %(value)s." + +#: core/validators.py:294 +#, python-format +msgid "This field must be given if %(field)s is not %(value)s" +msgstr "Este campo deve ser dado se o campo %(field)s não for %(value)s." + +#: core/validators.py:313 +msgid "Duplicate values are not allowed." +msgstr "Valores duplicados não são permitidos." + +#: core/validators.py:336 +#, python-format +msgid "This value must be a power of %s." +msgstr "Este valor deve ser uma potência de %s." + +#: core/validators.py:347 +msgid "Please enter a valid decimal number." +msgstr "Informe um número decimal válido." + +#: core/validators.py:349 +#, fuzzy, python-format +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "Por favor entre com um número decimal com no máximo %s digito." +msgstr[1] "Por favor entre com um número decimal com no máximo %s digitos." + +#: core/validators.py:352 +#, fuzzy, python-format +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "Informe um número decimal com no máximo %s casa decimal." +msgstr[1] "Informe um número decimal com no máximo %s casas decimais." + +#: core/validators.py:362 +#, python-format +msgid "Make sure your uploaded file is at least %s bytes big." +msgstr "Verifique se o arquivo enviado tem pelo menos %s bytes." + +#: core/validators.py:363 +#, python-format +msgid "Make sure your uploaded file is at most %s bytes big." +msgstr "Verifique se o arquivo enviado tem no máximo %s bytes." + +#: core/validators.py:376 +msgid "The format for this field is wrong." +msgstr "O formato deste campo está errado." + +#: core/validators.py:391 +msgid "This field is invalid." +msgstr "Este campo é inválido." + +#: core/validators.py:426 +#, python-format +msgid "Could not retrieve anything from %s." +msgstr "Não foi possível receber dados de %s." + +#: core/validators.py:429 +#, python-format +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"A URL %(url)s retornou um cabeçalho '%(contenttype)s' de Content-Type " +"inválido." + +#: core/validators.py:462 +#, python-format +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Por favor, feche a tag %(tag)s na linha %(line)s. (A linha começa com \"%" +"(start)s\".)" + +#: core/validators.py:466 +#, python-format +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Algum texto começando na linha %(line)s não é permitido no contexto. (Linha " +"começa com \"%(start)s\".)" + +#: core/validators.py:471 +#, python-format +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\" na linha %(line)s não é um atributo válido. (Linha começa com " +"\"%(start)s\".)" + +#: core/validators.py:476 +#, python-format +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\" na linha %(line)s é uma tag inválida. (Linha começa com \"%" +"(start)s\".)" + +#: core/validators.py:480 +#, python-format +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Uma tag na linha %(line)s está não apresenta um ou mais atributos exigidos." +"(Linha começa com \"%(start)s\".)" + +#: core/validators.py:485 +#, python-format +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"O atributo \"%(attr)s\" na linha %(line)s tem um valor inválido. (Linha " +"começa com \"%(start)s\".)" + +#: db/models/manipulators.py:302 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "%(object)s com este %(type)s já existe para o %(field)s dado." + +#: db/models/fields/__init__.py:40 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s com este %(fieldname)s já existe." + +#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 +#: forms/__init__.py:346 +msgid "This field is required." +msgstr "Este campo é requerido." + +#: db/models/fields/__init__.py:337 +#, fuzzy +msgid "This value must be an integer." +msgstr "Este valor deve ser um inteiro." + +#: db/models/fields/__init__.py:369 +#, fuzzy +msgid "This value must be either True or False." +msgstr "Este valor deve ser Verdadeiro ou Falso." + +#: db/models/fields/__init__.py:385 +#, fuzzy +msgid "This field cannot be null." +msgstr "Este campo não pode ser nulo." + +#: db/models/fields/__init__.py:562 +msgid "Enter a valid filename." +msgstr "Informe um nome de arquivo válido." + +#: db/models/fields/related.py:43 +#, python-format +msgid "Please enter a valid %s." +msgstr "Por favor informe um %s válido." + +#: db/models/fields/related.py:579 +#, fuzzy +msgid "Separate multiple IDs with commas." +msgstr "Separe IDs múltiplos com vírgulas." + +#: db/models/fields/related.py:581 +#, fuzzy +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" +" Mantenha pressionado \"Control\", ou \"Command\" no Mac para selecionar " +"mais de uma opção." + +#: db/models/fields/related.py:625 +#, fuzzy, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Por favor, entre IDs válidos para %(self)s. O valor %(value)r é inválido." +msgstr[1] "" +"Por favor, entre IDs válidos para %(self)s. Os valores %(value)r são inválidos." + +#: forms/__init__.py:380 +#, fuzzy, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "Certifique-se de que seu texto tenha menos que %s caractere." +msgstr[1] "Certifique-se de que seu texto tenha menos que %s caracteres." + +#: forms/__init__.py:385 +msgid "Line breaks are not allowed here." +msgstr "Não são permitidas quebras de linha aqui." + +#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "Selecione uma escolha válida; '%(data)s' não está em %(choices)s." + +#: forms/__init__.py:645 +msgid "The submitted file is empty." +msgstr "O arquivo enviado está vazio." + +#: forms/__init__.py:699 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "Informe um número inteiro entre -32.768 e 32.767" + +#: forms/__init__.py:708 +msgid "Enter a positive number." +msgstr "Informe um número positivo" + +#: forms/__init__.py:717 +msgid "Enter a whole number between 0 and 32,767." +msgstr "Informe um número inteiro entre 0 e 32.767." + +#: template/defaultfilters.py:379 +msgid "yes,no,maybe" +msgstr "sim,não,talvez" + +#~ msgid "Comment" +#~ msgstr "Comentário" + +#~ msgid "Comments" +#~ msgstr "Comentários" + +#~ msgid "String (up to 50)" +#~ msgstr "String (até 50)" + +#~ msgid "label" +#~ msgstr "etiqueta" + +#~ msgid "package" +#~ msgstr "pacote" + +#~ msgid "packages" +#~ msgstr "pacotes" + +#~ msgid "" +#~ "This comment was posted by a user who has posted fewer than %(count)s " +#~ "comment:\n" +#~ "\n" +#~ "%(text)sThis comment was posted by a user who has posted fewer than %" +#~ "(count)s comments:\n" +#~ "\n" +#~ "%(text)s" +#~ msgstr "" +#~ "Este comentário foi enviado por um usuário que enviou menos de %(count)s " +#~ "comentário:\n" +#~ "\n" +#~ "%(text)sEste comentário foi enviado por um usuário que enviou menos de %" +#~ "(count)s comentários:\n" +#~ "\n" +#~ "%(text)s" + +#, fuzzy +#~ msgid "count" +#~ msgstr "contagem" diff --git a/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.mo b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..31a2b1b3c0a38ee9d45020aae6ce083dfd73bbc3 GIT binary patch literal 1537 zcwSYKO=u)V6vxXXqi*77`~X4WfdpNo<4iPb9OJUHnFM#qB-zZY%N}dGpFOqPT{Tu! zMMfKlDa41-NIcS z$9SyC4K*({{-eR1Be`0xie8q9-jP&=92ZP0PHf47btWwwopK?zCdN1>NnVxw|G%PM zaIEW;f~Adfg-LIU%Tia;9@7qwZS4%CO()cwT2;V5rpuLPnBtgvRbDwyL!Npr$82do z^(Ju79L0$~r?n#nD$^>Tc(G@UhIvOEa)~H0qydfS8paL{#gGe5Jsq3MQaUjz<>%d5 zk~*WbQ0Yw)!wT0!O{T`EVi$OgGabFrq_VZMV^zRV`pUu)BNJ_Dti|cq*?F!P4y?I> zGm}eM@+MoSjP6o)L+act9`e*!pA1~CvgBfwyJTd#w0N*TO13OZkJzLyJ$GrX-Cj>R zYf1Z6>U6s6ofq1jb{m1oAs;DsSKPUz(@EML>b%l@c|B>rgt%Sld@{09yFz+nyR;>3 zVH|B|Xh3EtV>*ykA!#$M7dV?<$V;?+H8tgVr0cwrIVU5QWtR?6Ga57d+!1mwTItZZ z&8bhu_Cldnjm^(54lWJ0Pp{iqZ7*&a?OCIY<0|eh++DSG} z<3hP_u}!vhYBHtsE^UmJhcYusrPPzzr;w*gaRB7F;;66O)acolN38DXbarBuX{3sv zZI_y2BB?4RO^lVz1Z3n*W?a~W>lu|9$U^L@ati7CDl<@1G5aRRZjdi4YarhvwzUWS z&OS5L8%F^v6BIlNwt(&B#(4t`2MHs3zw%XJlrWq=zgIhAz)B|CD9^z)Nv5PgHtLL0 zwxUl%==chL!#i5W^_g5HgN$SG(G7ZjyJ8Z3Q{wSL0eO%%jTqHRx`NL80HuK;+5tN| T8B5&t4G32Bnm67@J4*2%{vCtm literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.po b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000..299fc65 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/pt_BR/LC_MESSAGES/djangojs.po @@ -0,0 +1,109 @@ +# Português do Brasil translation of django. +# Copyright (C) 2006 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Carlos Eduardo de Paula , 2006. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-12-09 11:51+0100\n" +"PO-Revision-Date: 2006-11-01 17:45-0300\n" +"Last-Translator: Carlos Eduardo de Paula \n" +"Language-Team: Português do Brasil \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit" + +#: contrib/admin/media/js/SelectFilter2.js:33 +#, perl-format +msgid "Available %s" +msgstr "%s Disponíveis" + +#: contrib/admin/media/js/SelectFilter2.js:41 +msgid "Choose all" +msgstr "Escolher todos" + +#: contrib/admin/media/js/SelectFilter2.js:46 +msgid "Add" +msgstr "Adicionar" + +#: contrib/admin/media/js/SelectFilter2.js:48 +msgid "Remove" +msgstr "Remover" + +#: contrib/admin/media/js/SelectFilter2.js:53 +#, perl-format +msgid "Chosen %s" +msgstr "%s escolhido(s)" + +#: contrib/admin/media/js/SelectFilter2.js:54 +msgid "Select your choice(s) and click " +msgstr "Selecione sua(s) escolha(s) e clique " + +#: contrib/admin/media/js/SelectFilter2.js:59 +msgid "Clear all" +msgstr "Limpar tudo" + +#: contrib/admin/media/js/dateparse.js:26 +#: contrib/admin/media/js/calendar.js:24 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "" +"Janeiro Fevereiro Março Abril Maio Junho Julho Agosto Setembro Outubro Novembro " +"Dezembro" +#: contrib/admin/media/js/dateparse.js:27 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "Domingo Segunda Terça Quarta Quinta Sexta Sábado" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "D S T Q Q S S" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80 +msgid "Now" +msgstr "Agora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +msgid "Clock" +msgstr "Relógio" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77 +msgid "Choose a time" +msgstr "Escolha uma hora" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81 +msgid "Midnight" +msgstr "Meia-noite" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82 +msgid "6 a.m." +msgstr "6 da manhã" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Noon" +msgstr "Meio-dia" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168 +msgid "Cancel" +msgstr "Cancelar" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162 +msgid "Today" +msgstr "Hoje" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114 +msgid "Calendar" +msgstr "Calendário" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160 +msgid "Yesterday" +msgstr "Ontem" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164 +msgid "Tomorrow" +msgstr "Amanhã" diff --git a/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.mo b/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..ffac5d5f9eace5cf718ff97511302ab1c4056c85 GIT binary patch literal 16327 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 literal 0 HcwPel00001 diff --git a/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.po b/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.po new file mode 100644 index 0000000..293e428 --- /dev/null +++ b/google_appengine/lib/django/django/conf/locale/ro/LC_MESSAGES/django.po @@ -0,0 +1,2005 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , 2005. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2006-05-16 10:12+0200\n" +"PO-Revision-Date: 2005-11-08 19:06+GMT+2\n" +"Last-Translator: Tiberiu Micu \n" +"Language-Team: Romanian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: contrib/comments/models.py:67 contrib/comments/models.py:166 +#, fuzzy +msgid "object ID" +msgstr "id obiect" + +#: contrib/comments/models.py:68 +msgid "headline" +msgstr "" + +#: contrib/comments/models.py:69 contrib/comments/models.py:90 +#: contrib/comments/models.py:167 +#, fuzzy +msgid "comment" +msgstr "conţinut" + +#: contrib/comments/models.py:70 +msgid "rating #1" +msgstr "" + +#: contrib/comments/models.py:71 +msgid "rating #2" +msgstr "" + +#: contrib/comments/models.py:72 +msgid "rating #3" +msgstr "" + +#: contrib/comments/models.py:73 +msgid "rating #4" +msgstr "" + +#: contrib/comments/models.py:74 +msgid "rating #5" +msgstr "" + +#: contrib/comments/models.py:75 +msgid "rating #6" +msgstr "" + +#: contrib/comments/models.py:76 +msgid "rating #7" +msgstr "" + +#: contrib/comments/models.py:77 +msgid "rating #8" +msgstr "" + +#: contrib/comments/models.py:82 +msgid "is valid rating" +msgstr "" + +#: contrib/comments/models.py:83 contrib/comments/models.py:169 +msgid "date/time submitted" +msgstr "" + +#: contrib/comments/models.py:84 contrib/comments/models.py:170 +msgid "is public" +msgstr "" + +#: contrib/comments/models.py:85 contrib/admin/views/doc.py:289 +#, fuzzy +msgid "IP address" +msgstr "adresa email" + +#: contrib/comments/models.py:86 +msgid "is removed" +msgstr "" + +#: contrib/comments/models.py:86 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" + +#: contrib/comments/models.py:91 +#, fuzzy +msgid "comments" +msgstr "conţinut" + +#: contrib/comments/models.py:131 contrib/comments/models.py:207 +#, fuzzy +msgid "Content object" +msgstr "tip conţinut" + +#: contrib/comments/models.py:159 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" + +#: contrib/comments/models.py:168 +#, fuzzy +msgid "person's name" +msgstr "Prenume" + +#: contrib/comments/models.py:171 +#, fuzzy +msgid "ip address" +msgstr "adresa email" + +#: contrib/comments/models.py:173 +msgid "approved by staff" +msgstr "" + +#: contrib/comments/models.py:176 +#, fuzzy +msgid "free comment" +msgstr "permite comentarii" + +#: contrib/comments/models.py:177 +#, fuzzy +msgid "free comments" +msgstr "permite comentarii" + +#: contrib/comments/models.py:233 +msgid "score" +msgstr "" + +#: contrib/comments/models.py:234 +#, fuzzy +msgid "score date" +msgstr "data expirare" + +#: contrib/comments/models.py:237 +msgid "karma score" +msgstr "" + +#: contrib/comments/models.py:238 +msgid "karma scores" +msgstr "" + +#: contrib/comments/models.py:242 +#, python-format +msgid "%(score)d rating by %(user)s" +msgstr "" + +#: contrib/comments/models.py:258 +#, python-format +msgid "" +"This comment was flagged by %(user)s:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/models.py:265 +#, fuzzy +msgid "flag date" +msgstr "pagina plată" + +#: contrib/comments/models.py:268 +#, fuzzy +msgid "user flag" +msgstr "Utilizator" + +#: contrib/comments/models.py:269 +#, fuzzy +msgid "user flags" +msgstr "Utilizatori" + +#: contrib/comments/models.py:273 +#, python-format +msgid "Flag by %r" +msgstr "" + +#: contrib/comments/models.py:278 +#, fuzzy +msgid "deletion date" +msgstr "date sesiune" + +#: contrib/comments/models.py:280 +msgid "moderator deletion" +msgstr "" + +#: contrib/comments/models.py:281 +msgid "moderator deletions" +msgstr "" + +#: contrib/comments/models.py:285 +#, python-format +msgid "Moderator deletion by %r" +msgstr "" + +#: contrib/comments/views/karma.py:19 +msgid "Anonymous users cannot vote" +msgstr "" + +#: contrib/comments/views/karma.py:23 +#, fuzzy +msgid "Invalid comment ID" +msgstr "permite comentarii" + +#: contrib/comments/views/karma.py:25 +msgid "No voting for yourself" +msgstr "" + +#: contrib/comments/views/comments.py:28 +msgid "" +"This rating is required because you've entered at least one other rating." +msgstr "" + +#: contrib/comments/views/comments.py:112 +#, python-format +msgid "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" +"\n" +"%(text)s" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" +"\n" +"%(text)s" +msgstr[0] "" +msgstr[1] "" + +#: contrib/comments/views/comments.py:117 +#, python-format +msgid "" +"This comment was posted by a sketchy user:\n" +"\n" +"%(text)s" +msgstr "" + +#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:280 +msgid "Only POSTs are allowed" +msgstr "" + +#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:284 +msgid "One or more of the required fields wasn't submitted" +msgstr "" + +#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:286 +msgid "Somebody tampered with the comment form (security violation)" +msgstr "" + +#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:292 +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" + +#: contrib/comments/views/comments.py:257 +#: contrib/comments/views/comments.py:321 +msgid "The comment form didn't provide either 'preview' or 'post'" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "Utilizator:" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "Parola:" + +#: contrib/comments/templates/comments/form.html:6 +#, fuzzy +msgid "Forgotten your password?" +msgstr "Schimbă-mi parola" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/base.html:23 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/model_index.html:5 +msgid "Log out" +msgstr "Deautentificare" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "" + +#: contrib/comments/templates/comments/form.html:27 +#: contrib/comments/templates/comments/freeform.html:5 +#, fuzzy +msgid "Comment:" +msgstr "permite comentarii" + +#: contrib/comments/templates/comments/form.html:32 +#: contrib/comments/templates/comments/freeform.html:9 +#, fuzzy +msgid "Preview comment" +msgstr "permite comentarii" + +#: contrib/comments/templates/comments/freeform.html:4 +#, fuzzy +msgid "Your name:" +msgstr "nume utilizator" + +#: contrib/admin/filterspecs.py:40 +#, python-format +msgid "" +"

                                                                                                  By %s:

                                                                                                  \n" +"